Skip to main content

semq_sys/
lib.rs

1// Copyright (c) 2026 The SEMQ Group Inc.
2// Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.md for terms.
3
4//! Raw FFI declarations for the SEMQ core.
5//!
6//! Mirrors `include/semq.h` one-to-one: the constants, every enum value,
7//! the plain structs, the opaque handles and all exported functions. Nothing
8//! here is safe; the `semq` crate builds the safe API on top. The
9//! declarations are written by hand so the build needs no libclang; layout
10//! tests pin the structs whose size and offsets the ABI depends on.
11//!
12//! C enums are declared as `u32` aliases with one constant per value. Every
13//! function that can fail returns a [`semq_status_t`] and fills the
14//! [`semq_error_t`] it is given, when non-null.
15
16#![allow(non_camel_case_types)]
17
18use std::marker::{PhantomData, PhantomPinned};
19use std::os::raw::{c_char, c_int};
20
21// --------------------------------------------------------------------------
22// Limits and constants
23// --------------------------------------------------------------------------
24
25/// File format version written and accepted by this core.
26pub const SEMQ_FILE_VERSION: u32 = 2;
27/// Operator rule revision written into every config (`p2`).
28pub const SEMQ_RULE_REVISION: u32 = 0;
29/// Size of the canonical config form in bytes.
30pub const SEMQ_CONFIG_BYTES: u32 = 13;
31/// Size of each identity (`content_digest`, `state_id`) in bytes.
32pub const SEMQ_DIGEST_BYTES: u32 = 32;
33/// Largest accepted vector dimension.
34pub const SEMQ_MAX_DIM: u32 = 65536;
35/// Largest `utf8` id, in bytes. Ids are at least one byte.
36pub const SEMQ_ID_MAX_BYTES: u32 = 4096;
37/// Manifest limits.
38pub const SEMQ_MANIFEST_MAX_PAIRS: u32 = 4096;
39pub const SEMQ_MANIFEST_KEY_MAX: u32 = 256;
40pub const SEMQ_MANIFEST_VALUE_MAX: u32 = 65536;
41pub const SEMQ_MANIFEST_SECTION_MAX: u32 = 16 * 1024 * 1024;
42/// Sentinel for "no row" / "no field" in [`semq_error_t`] and index lookups.
43pub const SEMQ_NONE: u64 = u64::MAX;
44
45/// Operators. Values are pinned: they appear in the canonical config form.
46pub type semq_operator_t = u32;
47pub const SEMQ_ORBIT: semq_operator_t = 0;
48pub const SEMQ_PHASE: semq_operator_t = 1;
49pub const SEMQ_QUANT: semq_operator_t = 2;
50
51/// Identifier kinds. Values are pinned: they appear in files.
52pub type semq_id_kind_t = u32;
53pub const SEMQ_ID_U64: semq_id_kind_t = 0;
54pub const SEMQ_ID_UTF8: semq_id_kind_t = 1;
55
56/// Status codes. Each maps to exactly one host error type.
57pub type semq_status_t = u32;
58pub const SEMQ_OK: semq_status_t = 0;
59/// An argument violates its contract.
60pub const SEMQ_ERR_INVALID_INPUT: semq_status_t = 1;
61/// Two states differ in config or id kind.
62pub const SEMQ_ERR_INCOMPATIBLE: semq_status_t = 2;
63/// A file image is not a valid v2 image.
64pub const SEMQ_ERR_FORMAT: semq_status_t = 3;
65/// A file image's digest does not match.
66pub const SEMQ_ERR_INTEGRITY: semq_status_t = 4;
67/// The operation or FP environment is not supported.
68pub const SEMQ_ERR_UNSUPPORTED: semq_status_t = 5;
69/// An allocation failed.
70pub const SEMQ_ERR_NOMEM: semq_status_t = 6;
71/// A defect in the core; report it.
72pub const SEMQ_ERR_INTERNAL: semq_status_t = 7;
73
74/// Which integrity check failed on load.
75pub type semq_which_t = u32;
76pub const SEMQ_WHICH_NONE: semq_which_t = 0;
77pub const SEMQ_WHICH_CONTENT: semq_which_t = 1;
78pub const SEMQ_WHICH_STATE: semq_which_t = 2;
79
80/// File sections named by `field` in [`SEMQ_ERR_FORMAT`] errors.
81pub type semq_section_t = u32;
82pub const SEMQ_SECTION_FRAMING: semq_section_t = 0;
83pub const SEMQ_SECTION_CONFIG: semq_section_t = 1;
84pub const SEMQ_SECTION_SIZES: semq_section_t = 2;
85pub const SEMQ_SECTION_IDS: semq_section_t = 3;
86pub const SEMQ_SECTION_MANIFEST: semq_section_t = 4;
87pub const SEMQ_SECTION_FOOTER: semq_section_t = 5;
88pub const SEMQ_SECTION_ROWS: semq_section_t = 6;
89
90/// Config fields named by `field` in [`SEMQ_ERR_INVALID_INPUT`] errors raised
91/// by the config constructors.
92pub type semq_config_field_t = u32;
93pub const SEMQ_FIELD_OPERATOR: semq_config_field_t = 0;
94pub const SEMQ_FIELD_DIM: semq_config_field_t = 1;
95/// scale, sectors or bins.
96pub const SEMQ_FIELD_P1: semq_config_field_t = 2;
97pub const SEMQ_FIELD_P2: semq_config_field_t = 3;
98
99/// Which id list of a Diff an accessor refers to.
100pub type semq_list_t = u32;
101pub const SEMQ_LIST_ADDED: semq_list_t = 0;
102pub const SEMQ_LIST_REMOVED: semq_list_t = 1;
103pub const SEMQ_LIST_CHANGED: semq_list_t = 2;
104
105/// Checks beyond those of `semq_diff_within`: flags combined with `|`, for
106/// `semq_floor_measure_for` and `semq_diff_evaluate`.
107pub type semq_check_t = u32;
108pub const SEMQ_CHECK_PER_ROW: semq_check_t = 1;
109
110/// Why a verdict failed: flags combined with `|`.
111pub type semq_reason_t = u32;
112pub const SEMQ_REASON_NO_COMMON_ROWS: semq_reason_t = 1;
113pub const SEMQ_REASON_REMOVED_ROWS: semq_reason_t = 2;
114pub const SEMQ_REASON_CHANGED_RATIO: semq_reason_t = 4;
115pub const SEMQ_REASON_HAMMING: semq_reason_t = 8;
116pub const SEMQ_REASON_ENCODER: semq_reason_t = 16;
117pub const SEMQ_REASON_ROW_ABOVE_MAX: semq_reason_t = 32;
118
119// --------------------------------------------------------------------------
120// Plain structs
121// --------------------------------------------------------------------------
122
123/// Error report. `status` is the returned status; `which` is set only for
124/// [`SEMQ_ERR_INTEGRITY`]; `row` and `field` are [`SEMQ_NONE`] when they do
125/// not apply. `message` is always NUL-terminated and never contains input
126/// data.
127#[repr(C)]
128#[derive(Debug, Clone, Copy)]
129pub struct semq_error_t {
130    pub status: u32,
131    pub which: u32,
132    pub row: u64,
133    pub field: u64,
134    pub message: [c_char; 128],
135}
136
137/// A codec configuration. `op` is a [`semq_operator_t`]; `p1` is `scale`
138/// (orbit), `sectors` (phase) or `bins` (quant); `p2` is the rule revision.
139#[repr(C)]
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub struct semq_config_t {
142    pub op: u32,
143    pub dim: u32,
144    pub p1: u32,
145    pub p2: u32,
146}
147
148/// One manifest pair. Keys and values are UTF-8 bytes, not NUL-terminated.
149#[repr(C)]
150#[derive(Debug, Clone, Copy)]
151pub struct semq_pair_t {
152    pub key: *const u8,
153    pub key_len: u32,
154    pub value: *const u8,
155    pub value_len: u32,
156}
157
158/// Identifiers of `n` rows, in input order (any order; the core sorts).
159/// For [`SEMQ_ID_U64`] pass `u64` (n entries). For [`SEMQ_ID_UTF8`] pass
160/// `utf8_offsets` (n + 1 entries, `offsets[0] = 0`, non-decreasing) and
161/// `utf8_bytes` (`offsets[n]` bytes): id `i` is `bytes[offsets[i] .. offsets[i+1])`.
162#[repr(C)]
163#[derive(Debug, Clone, Copy)]
164pub struct semq_ids_t {
165    pub kind: u32,
166    pub n: u64,
167    pub u64: *const u64,
168    pub utf8_offsets: *const u64,
169    pub utf8_bytes: *const u8,
170}
171
172/// One borrowed part of the file image.
173#[repr(C)]
174#[derive(Debug, Clone, Copy)]
175pub struct semq_part_t {
176    pub ptr: *const u8,
177    pub len: u64,
178}
179
180/// A change to one manifest key. `has_before`/`has_after` are 0 when the
181/// key is absent on that side; the pointers are then NULL.
182#[repr(C)]
183#[derive(Debug, Clone, Copy)]
184pub struct semq_manifest_change_t {
185    pub key: *const u8,
186    pub key_len: u32,
187    pub has_before: c_int,
188    pub before: *const u8,
189    pub before_len: u32,
190    pub has_after: c_int,
191    pub after: *const u8,
192    pub after_len: u32,
193}
194
195// --------------------------------------------------------------------------
196// Opaque handles
197// --------------------------------------------------------------------------
198
199/// Opaque codec handle (`struct semq_codec`).
200#[repr(C)]
201pub struct semq_codec_t {
202    _data: [u8; 0],
203    _marker: PhantomData<(*mut u8, PhantomPinned)>,
204}
205
206/// Opaque encoding handle (`struct semq_encoding`).
207#[repr(C)]
208pub struct semq_encoding_t {
209    _data: [u8; 0],
210    _marker: PhantomData<(*mut u8, PhantomPinned)>,
211}
212
213/// Opaque diff handle (`struct semq_diff`).
214#[repr(C)]
215pub struct semq_diff_t {
216    _data: [u8; 0],
217    _marker: PhantomData<(*mut u8, PhantomPinned)>,
218}
219
220/// Opaque floor handle (`struct semq_floor`).
221#[repr(C)]
222pub struct semq_floor_t {
223    _data: [u8; 0],
224    _marker: PhantomData<(*mut u8, PhantomPinned)>,
225}
226
227/// Opaque verdict handle (`struct semq_verdict`).
228#[repr(C)]
229pub struct semq_verdict_t {
230    _data: [u8; 0],
231    _marker: PhantomData<(*mut u8, PhantomPinned)>,
232}
233
234// --------------------------------------------------------------------------
235// Functions (every SEMQ_API declaration in semq.h, in header order)
236// --------------------------------------------------------------------------
237
238extern "C" {
239    // CodecConfig
240    pub fn semq_config_orbit(
241        dim: u32,
242        scale: u32,
243        out: *mut semq_config_t,
244        err: *mut semq_error_t,
245    ) -> semq_status_t;
246    pub fn semq_config_phase(
247        dim: u32,
248        sectors: u32,
249        out: *mut semq_config_t,
250        err: *mut semq_error_t,
251    ) -> semq_status_t;
252    pub fn semq_config_quant(
253        dim: u32,
254        bins: u32,
255        out: *mut semq_config_t,
256        err: *mut semq_error_t,
257    ) -> semq_status_t;
258    pub fn semq_config_validate(cfg: *const semq_config_t, err: *mut semq_error_t)
259        -> semq_status_t;
260    /// Write the 13-byte canonical form. `cfg` must be valid.
261    pub fn semq_config_to_bytes(cfg: *const semq_config_t, out: *mut u8);
262    /// Parse a 13-byte canonical form.
263    pub fn semq_config_from_bytes(
264        input: *const u8,
265        out: *mut semq_config_t,
266        err: *mut semq_error_t,
267    ) -> semq_status_t;
268    pub fn semq_config_bytes_per_vector(cfg: *const semq_config_t) -> u32;
269    pub fn semq_config_units_per_row(cfg: *const semq_config_t) -> u32;
270    /// quant only: `(float)(2.0 / sqrt((double)dim))`; 0 for other operators.
271    pub fn semq_config_max_magnitude(cfg: *const semq_config_t) -> f32;
272    /// 1 when the two configs have the same canonical bytes.
273    pub fn semq_config_equal(a: *const semq_config_t, b: *const semq_config_t) -> c_int;
274
275    // Codec
276    pub fn semq_codec_create(
277        cfg: *const semq_config_t,
278        out: *mut *mut semq_codec_t,
279        err: *mut semq_error_t,
280    ) -> semq_status_t;
281    pub fn semq_codec_free(codec: *mut semq_codec_t);
282    pub fn semq_codec_config(codec: *const semq_codec_t) -> *const semq_config_t;
283    /// Name of the kernel this codec runs ("scalar", "neon", "avx2", ...).
284    pub fn semq_codec_backend(codec: *const semq_codec_t) -> *const c_char;
285    pub fn semq_codec_encode(
286        codec: *const semq_codec_t,
287        ids: *const semq_ids_t,
288        vectors: *const f32,
289        manifest: *const semq_pair_t,
290        n_pairs: u32,
291        out: *mut *mut semq_encoding_t,
292        err: *mut semq_error_t,
293    ) -> semq_status_t;
294    /// Representatives, `n x dim` float32, row `i` for `ids[i]`.
295    pub fn semq_codec_decode(
296        codec: *const semq_codec_t,
297        enc: *const semq_encoding_t,
298        out: *mut f32,
299        err: *mut semq_error_t,
300    ) -> semq_status_t;
301    /// Symbols, `n x units_per_row` bytes, one per unit.
302    pub fn semq_codec_unpack(
303        codec: *const semq_codec_t,
304        enc: *const semq_encoding_t,
305        out: *mut u8,
306        err: *mut semq_error_t,
307    ) -> semq_status_t;
308
309    // Encoding
310    pub fn semq_encoding_create(
311        cfg: *const semq_config_t,
312        ids: *const semq_ids_t,
313        rows: *const u8,
314        manifest: *const semq_pair_t,
315        n_pairs: u32,
316        out: *mut *mut semq_encoding_t,
317        err: *mut semq_error_t,
318    ) -> semq_status_t;
319    pub fn semq_encoding_free(enc: *mut semq_encoding_t);
320    pub fn semq_encoding_config(enc: *const semq_encoding_t) -> *const semq_config_t;
321    pub fn semq_encoding_id_kind(enc: *const semq_encoding_t) -> u32;
322    pub fn semq_encoding_len(enc: *const semq_encoding_t) -> u64;
323    /// Rows in id order, `n x bytes_per_vector` bytes.
324    pub fn semq_encoding_rows(enc: *const semq_encoding_t, len: *mut u64) -> *const u8;
325    /// Sorted ids. `u64` kinds: `n` values.
326    pub fn semq_encoding_ids_u64(enc: *const semq_encoding_t) -> *const u64;
327    /// `utf8` kinds: `n + 1` offsets.
328    pub fn semq_encoding_ids_utf8_offsets(enc: *const semq_encoding_t) -> *const u64;
329    /// `utf8` kinds: the id bytes.
330    pub fn semq_encoding_ids_utf8_bytes(enc: *const semq_encoding_t, len: *mut u64) -> *const u8;
331    /// Manifest pairs in canonical (bytewise key) order.
332    pub fn semq_encoding_manifest_len(enc: *const semq_encoding_t) -> u32;
333    pub fn semq_encoding_manifest_pair(
334        enc: *const semq_encoding_t,
335        i: u32,
336        out: *mut semq_pair_t,
337        err: *mut semq_error_t,
338    ) -> semq_status_t;
339    pub fn semq_encoding_content_digest(enc: *const semq_encoding_t, out: *mut u8);
340    pub fn semq_encoding_state_id(enc: *const semq_encoding_t, out: *mut u8);
341    /// Row index of an id, or [`SEMQ_NONE`] when absent.
342    pub fn semq_encoding_find_u64(
343        enc: *const semq_encoding_t,
344        id: u64,
345        index: *mut u64,
346        err: *mut semq_error_t,
347    ) -> semq_status_t;
348    pub fn semq_encoding_find_utf8(
349        enc: *const semq_encoding_t,
350        id: *const u8,
351        len: u64,
352        index: *mut u64,
353        err: *mut semq_error_t,
354    ) -> semq_status_t;
355    /// Total size of the file image.
356    pub fn semq_encoding_file_size(enc: *const semq_encoding_t) -> u64;
357    /// The file image as 5 ordered parts owned by the handle. Returns 5.
358    pub fn semq_encoding_save_parts(enc: *const semq_encoding_t, parts: *mut semq_part_t) -> u32;
359    /// Write the whole file image into `out` (`cap` >= file size).
360    pub fn semq_encoding_save(
361        enc: *const semq_encoding_t,
362        out: *mut u8,
363        cap: u64,
364        err: *mut semq_error_t,
365    ) -> semq_status_t;
366    /// Parse a complete file image. The bytes are copied.
367    pub fn semq_encoding_load(
368        buf: *const u8,
369        len: u64,
370        out: *mut *mut semq_encoding_t,
371        err: *mut semq_error_t,
372    ) -> semq_status_t;
373    /// Merge `k` Encodings with the same config, id kind and manifest and
374    /// pairwise-disjoint ids.
375    pub fn semq_encoding_concat(
376        parts: *const *const semq_encoding_t,
377        k: u32,
378        out: *mut *mut semq_encoding_t,
379        err: *mut semq_error_t,
380    ) -> semq_status_t;
381    /// `reference.diff(candidate)`.
382    pub fn semq_encoding_diff(
383        reference: *const semq_encoding_t,
384        candidate: *const semq_encoding_t,
385        out: *mut *mut semq_diff_t,
386        err: *mut semq_error_t,
387    ) -> semq_status_t;
388
389    // Diff
390    /// The Diff keeps both Encodings alive until it is freed.
391    pub fn semq_diff_free(diff: *mut semq_diff_t);
392    pub fn semq_diff_config(diff: *const semq_diff_t) -> *const semq_config_t;
393    pub fn semq_diff_id_kind(diff: *const semq_diff_t) -> u32;
394    pub fn semq_diff_reference_id(diff: *const semq_diff_t, out: *mut u8);
395    pub fn semq_diff_candidate_id(diff: *const semq_diff_t, out: *mut u8);
396    pub fn semq_diff_count(diff: *const semq_diff_t, list: u32) -> u64;
397    pub fn semq_diff_n_unchanged(diff: *const semq_diff_t) -> u64;
398    /// Id `i` of a list, in canonical order. For `u64` kinds `*out_u64` is
399    /// set; for `utf8` kinds `*bytes`/`*len` are set.
400    pub fn semq_diff_id(
401        diff: *const semq_diff_t,
402        list: u32,
403        i: u64,
404        out_u64: *mut u64,
405        bytes: *mut *const u8,
406        len: *mut u64,
407        err: *mut semq_error_t,
408    ) -> semq_status_t;
409    /// Hamming distance of changed id `i` (units whose symbol differs).
410    pub fn semq_diff_hamming(
411        diff: *const semq_diff_t,
412        i: u64,
413        out: *mut u64,
414        err: *mut semq_error_t,
415    ) -> semq_status_t;
416    pub fn semq_diff_manifest_changes(diff: *const semq_diff_t) -> u32;
417    pub fn semq_diff_manifest_change(
418        diff: *const semq_diff_t,
419        i: u32,
420        out: *mut semq_manifest_change_t,
421        err: *mut semq_error_t,
422    ) -> semq_status_t;
423    /// Differing units of an id present on both sides. Writes up to `cap`
424    /// entries and the total count into `*count`. Pass `cap = 0` to query.
425    pub fn semq_diff_units_u64(
426        diff: *const semq_diff_t,
427        id: u64,
428        units: *mut u32,
429        ref_symbols: *mut u8,
430        cand_symbols: *mut u8,
431        cap: u64,
432        count: *mut u64,
433        err: *mut semq_error_t,
434    ) -> semq_status_t;
435    pub fn semq_diff_units_utf8(
436        diff: *const semq_diff_t,
437        id: *const u8,
438        len: u64,
439        units: *mut u32,
440        ref_symbols: *mut u8,
441        cand_symbols: *mut u8,
442        cap: u64,
443        count: *mut u64,
444        err: *mut semq_error_t,
445    ) -> semq_status_t;
446
447    // Floor
448    /// A floor from its fields. `config` must validate, `id_kind` must be
449    /// 0 or 1, `reference_id` is 32 bytes, `nulls >= 1`, `total_rows >= 1`,
450    /// `changed_rows <= total_rows` and `hamming <= units_per_row(config)`;
451    /// otherwise `SEMQ_ERR_INVALID_INPUT`.
452    pub fn semq_floor_create(
453        config: *const semq_config_t,
454        id_kind: u32,
455        reference_id: *const u8,
456        nulls: u64,
457        changed_rows: u64,
458        total_rows: u64,
459        hamming: u64,
460        out: *mut *mut semq_floor_t,
461        err: *mut semq_error_t,
462    ) -> semq_status_t;
463    pub fn semq_floor_free(floor: *mut semq_floor_t);
464    /// Envelope of `k >= 1` null diffs; every input diff is within the
465    /// result. All nulls must share config, id kind and reference state_id
466    /// (`SEMQ_ERR_INCOMPATIBLE`, `field` = the offending index) and each
467    /// must be a valid null (`SEMQ_ERR_INVALID_INPUT`, `field` = index).
468    pub fn semq_floor_measure(
469        nulls: *const *const semq_diff_t,
470        k: u32,
471        out: *mut *mut semq_floor_t,
472        err: *mut semq_error_t,
473    ) -> semq_status_t;
474    /// `semq_floor_measure` that also records what the `checks`
475    /// (`SEMQ_CHECK_*` flags) need: with `SEMQ_CHECK_PER_ROW`, `max_hamming`
476    /// and `distinct_nulls`. An unknown bit is `SEMQ_ERR_INVALID_INPUT`.
477    pub fn semq_floor_measure_for(
478        nulls: *const *const semq_diff_t,
479        k: u32,
480        checks: u32,
481        out: *mut *mut semq_floor_t,
482        err: *mut semq_error_t,
483    ) -> semq_status_t;
484    /// The config, owned by the handle.
485    pub fn semq_floor_config(floor: *const semq_floor_t) -> *const semq_config_t;
486    /// 0 = u64, 1 = utf8.
487    pub fn semq_floor_id_kind(floor: *const semq_floor_t) -> u32;
488    /// Writes the 32-byte state_id of the reference into `out`.
489    pub fn semq_floor_reference_id(floor: *const semq_floor_t, out: *mut u8);
490    pub fn semq_floor_nulls(floor: *const semq_floor_t) -> u64;
491    pub fn semq_floor_changed_rows(floor: *const semq_floor_t) -> u64;
492    pub fn semq_floor_total_rows(floor: *const semq_floor_t) -> u64;
493    pub fn semq_floor_hamming(floor: *const semq_floor_t) -> u64;
494    /// `SEMQ_NONE` for a floor without per-row data.
495    pub fn semq_floor_max_hamming(floor: *const semq_floor_t) -> u64;
496    /// Distinct null states among the nulls; `SEMQ_NONE` when not recorded.
497    pub fn semq_floor_distinct_nulls(floor: *const semq_floor_t) -> u64;
498    /// Length in bytes of the floor's JSON form.
499    pub fn semq_floor_json_size(floor: *const semq_floor_t) -> u64;
500    /// Writes the floor's JSON form into `out` (`cap >= semq_floor_json_size`).
501    pub fn semq_floor_save(
502        floor: *const semq_floor_t,
503        out: *mut u8,
504        cap: u64,
505        err: *mut semq_error_t,
506    ) -> semq_status_t;
507    /// Reads a floor from its JSON form; strict on the schema's keys, other
508    /// keys ignored. `SEMQ_ERR_INVALID_INPUT` on any violation.
509    pub fn semq_floor_load(
510        buf: *const u8,
511        len: u64,
512        out: *mut *mut semq_floor_t,
513        err: *mut semq_error_t,
514    ) -> semq_status_t;
515    /// `*out = 1` iff the diff is within the floor (exact integer
516    /// arithmetic). The floor must match the diff's config, id kind and
517    /// reference state_id (`SEMQ_ERR_INCOMPATIBLE` otherwise).
518    pub fn semq_diff_within(
519        diff: *const semq_diff_t,
520        floor: *const semq_floor_t,
521        out: *mut c_int,
522        err: *mut semq_error_t,
523    ) -> semq_status_t;
524
525    // Gate evaluation
526    /// With `checks == 0`, `passed` equals `semq_diff_within`; an unknown
527    /// bit is `SEMQ_ERR_INVALID_INPUT`. `SEMQ_ERR_INCOMPATIBLE` for a floor of
528    /// another context, or for `SEMQ_CHECK_PER_ROW` on a floor without
529    /// `max_hamming`.
530    pub fn semq_diff_evaluate(
531        diff: *const semq_diff_t,
532        floor: *const semq_floor_t,
533        checks: u32,
534        out: *mut *mut semq_verdict_t,
535        err: *mut semq_error_t,
536    ) -> semq_status_t;
537    pub fn semq_verdict_free(verdict: *mut semq_verdict_t);
538    pub fn semq_verdict_passed(verdict: *const semq_verdict_t) -> c_int;
539    /// `SEMQ_REASON_*` flags; 0 when the verdict passed.
540    pub fn semq_verdict_reasons(verdict: *const semq_verdict_t) -> u32;
541    pub fn semq_verdict_row_count(verdict: *const semq_verdict_t) -> u64;
542    /// Index in the diff's changed list; `SEMQ_NONE` when out of range.
543    pub fn semq_verdict_row(verdict: *const semq_verdict_t, i: u64) -> u64;
544
545    // Build information and utilities
546    /// Core version string, from the repository `VERSION` file.
547    pub fn semq_core_version() -> *const c_char;
548    /// Compile-time build recipe identifier. Not a provenance proof.
549    pub fn semq_build_id() -> *const c_char;
550    /// Effective kernel for an operator on this host ("scalar", "neon", ...).
551    pub fn semq_backend_name(op: u32) -> *const c_char;
552    /// Static name of a status code ("ok", "invalid_input", ...).
553    pub fn semq_status_name(status: u32) -> *const c_char;
554    /// SHA-256 (FIPS 180-4) of `len` bytes.
555    pub fn semq_sha256(data: *const u8, len: u64, out: *mut u8);
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561    use std::ffi::CStr;
562    use std::mem::{align_of, offset_of, size_of};
563
564    /// `semq_error_t` is `u32, u32, u64, u64, char[128]`: 152 bytes.
565    #[test]
566    fn error_layout_matches_the_abi() {
567        assert_eq!(size_of::<semq_error_t>(), 152);
568        assert_eq!(align_of::<semq_error_t>(), 8);
569        assert_eq!(offset_of!(semq_error_t, status), 0);
570        assert_eq!(offset_of!(semq_error_t, which), 4);
571        assert_eq!(offset_of!(semq_error_t, row), 8);
572        assert_eq!(offset_of!(semq_error_t, field), 16);
573        assert_eq!(offset_of!(semq_error_t, message), 24);
574    }
575
576    /// `semq_config_t` is four `u32`: 16 bytes.
577    #[test]
578    fn config_layout_matches_the_abi() {
579        assert_eq!(size_of::<semq_config_t>(), 16);
580        assert_eq!(align_of::<semq_config_t>(), 4);
581        assert_eq!(offset_of!(semq_config_t, op), 0);
582        assert_eq!(offset_of!(semq_config_t, dim), 4);
583        assert_eq!(offset_of!(semq_config_t, p1), 8);
584        assert_eq!(offset_of!(semq_config_t, p2), 12);
585    }
586
587    /// Pointer-bearing structs, on 64-bit hosts (the core's target).
588    #[cfg(target_pointer_width = "64")]
589    #[test]
590    fn pointer_struct_layouts_match_the_abi() {
591        assert_eq!(size_of::<semq_pair_t>(), 32);
592        assert_eq!(offset_of!(semq_pair_t, key), 0);
593        assert_eq!(offset_of!(semq_pair_t, key_len), 8);
594        assert_eq!(offset_of!(semq_pair_t, value), 16);
595        assert_eq!(offset_of!(semq_pair_t, value_len), 24);
596
597        assert_eq!(size_of::<semq_ids_t>(), 40);
598        assert_eq!(offset_of!(semq_ids_t, kind), 0);
599        assert_eq!(offset_of!(semq_ids_t, n), 8);
600        assert_eq!(offset_of!(semq_ids_t, u64), 16);
601        assert_eq!(offset_of!(semq_ids_t, utf8_offsets), 24);
602        assert_eq!(offset_of!(semq_ids_t, utf8_bytes), 32);
603
604        assert_eq!(size_of::<semq_part_t>(), 16);
605        assert_eq!(offset_of!(semq_part_t, ptr), 0);
606        assert_eq!(offset_of!(semq_part_t, len), 8);
607
608        assert_eq!(size_of::<semq_manifest_change_t>(), 48);
609        assert_eq!(offset_of!(semq_manifest_change_t, key), 0);
610        assert_eq!(offset_of!(semq_manifest_change_t, key_len), 8);
611        assert_eq!(offset_of!(semq_manifest_change_t, has_before), 12);
612        assert_eq!(offset_of!(semq_manifest_change_t, before), 16);
613        assert_eq!(offset_of!(semq_manifest_change_t, before_len), 24);
614        assert_eq!(offset_of!(semq_manifest_change_t, has_after), 28);
615        assert_eq!(offset_of!(semq_manifest_change_t, after), 32);
616        assert_eq!(offset_of!(semq_manifest_change_t, after_len), 40);
617    }
618
619    /// The library links and the simplest entry points answer as the header
620    /// documents: a quant(4, 4) config and its canonical form.
621    #[test]
622    fn core_links_and_answers() {
623        let version = unsafe { CStr::from_ptr(semq_core_version()) };
624        assert!(!version.to_bytes().is_empty());
625
626        let mut cfg = semq_config_t {
627            op: 0,
628            dim: 0,
629            p1: 0,
630            p2: 0,
631        };
632        let status = unsafe { semq_config_quant(4, 4, &mut cfg, std::ptr::null_mut()) };
633        assert_eq!(status, SEMQ_OK);
634        assert_eq!(
635            cfg,
636            semq_config_t {
637                op: SEMQ_QUANT,
638                dim: 4,
639                p1: 4,
640                p2: SEMQ_RULE_REVISION
641            }
642        );
643        let mut bytes = [0u8; 13];
644        unsafe { semq_config_to_bytes(&cfg, bytes.as_mut_ptr()) };
645        assert_eq!(bytes, [2, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]);
646
647        let name = unsafe { CStr::from_ptr(semq_status_name(SEMQ_ERR_INVALID_INPUT)) };
648        assert_eq!(name.to_str().unwrap(), "invalid_input");
649    }
650}