ordvec 0.5.0

Training-free ordinal & sign quantization for vector retrieval
Documentation
# Bindings Safety and Ownership

This is the cross-language contract for embedders using the Rust crate, Python
package, C ABI, or Go wrapper. It consolidates the binding notes that otherwise
live near each implementation. It does not add a new runtime policy: callers
still own scheduling, path trust, input mutability, and deployment provenance.

## Concurrency

`ordvec` is read-concurrent and mutation-exclusive.

- Rust index values can be searched concurrently through shared references.
  Mutation methods such as `add` require exclusive access.
- Python search, candidate-generation, scoring, and `add` methods release the
  GIL while Rust performs the heavy work. PyO3 still enforces object borrow
  rules, and the binding copies NumPy input arrays into Rust-owned buffers
  before releasing the GIL. Large calls may temporarily require an additional
  input-sized buffer.
- The C ABI permits concurrent `ordvec_index_search`,
  `ordvec_index_probe`, and `ordvec_index_info` calls on one loaded handle.
  `ordvec_index_free` must not race with any other call on that handle.
- The Go wrapper serializes `Close` against `Search` and `Info`; after
  `Close`, both methods return `ErrClosed`.

## Borrowed Inputs

Caller-provided Rust slices, C buffers, and Go slices are borrowed for the
duration of the call and are not retained after the function returns. Python
NumPy inputs that cross a GIL-released call are copied before the GIL is
released.

- Do not mutate Rust slices, C buffers, or Go slices while a call that received
  them is in progress.
- Query, corpus, candidate, output, hit, and stats buffers remain caller-owned
  unless a specific API says otherwise.
- Candidate lists are entry lists, not sets. Duplicate candidate IDs are scored
  independently, count toward candidate and vector-scored statistics, and can
  produce duplicate hits. Deduplicate before calling when unique row IDs or
  waste-free scoring matter.

## Returned Memory

Current C ABI search calls do not return heap-owned result buffers. Callers
allocate and retain ownership of `hits_out`, `returned_out`, and `stats_out`.
`ordvec_index_load` returns an opaque handle that must be freed exactly once
with `ordvec_index_free` after all concurrent calls using that handle have
finished; ABI v1 has no general `ordvec_free` for result memory.

The Go wrapper copies C search hits into Go-owned slices. It frees temporary
`C.CString` values internally and releases the C index handle through `Close`
or its finalizer; Go callers do not free C result buffers.

## Rows and External IDs

Core search results use internal row ordinals. The primitive persisted formats
do not carry an application ID map.

`ordvec-manifest` can bind an application-owned ID sidecar as a required
auxiliary artifact, but the primitive Rust, C, Go, and Python search paths still
return row ordinals. Host systems should maintain their own row-to-application
ID map and verify it together with the index when crossing a trust boundary.

## Paths and Trust

`write` and `load` paths are trusted input. The core crate, Python binding, C
ABI, and Go wrapper forward paths to the filesystem without path traversal
sanitization or sandboxing.

Services that derive paths from user input should canonicalize and constrain
paths before calling `ordvec`, or use an application storage layer that never
exposes raw path choice to callers. Resolve paths against an allowed base
directory after symlink resolution, then reject any resolved path outside that
base. In Rust, use `std::fs::canonicalize`; in Python, use `pathlib.Path.resolve`;
in Go, combine lexical cleanup such as `filepath.Clean` with symlink-aware
resolution such as `filepath.EvalSymlinks`. For artifact integrity and sidecar
binding, use `ordvec-manifest`; it verifies hashes, declared metadata,
auxiliary artifacts, and attestation shape, but it does not sign files or
decide key policy.

## Errors and Panics

- The Rust crate keeps fail-loud panicking constructors and methods where that
  is the documented API. Existing `try_*` helpers return `OrdvecError` only
  where explicitly provided.
- Python validates dimensions, dtypes, contiguity where required, finite
  values, candidate ranges, and capacities at the boundary so common invalid
  inputs raise typed Python exceptions instead of surfacing opaque Rust panics.
- The C ABI catches Rust panics at status-returning FFI boundaries and returns
  `ORDVEC_STATUS_PANIC`; no Rust unwind crosses the ABI boundary. The same
  thread's `ordvec_last_error()` is set to the panic payload when it is a string
  or to fallback panic text otherwise. Successful status-returning calls clear
  that thread-local error. Non-status helpers such as `ordvec_last_error()`,
  version/status accessors, and `ordvec_index_free` do not report fallible
  status.
- The Go wrapper maps C status values to Go errors and preserves the C ABI
  pointer and lifetime rules.

## Release Review Checklist

When a change touches a binding, review these questions before release:

- Does the change preserve read-concurrent, mutation-exclusive behavior?
- Are borrowed buffers still borrowed only for the documented call duration?
- Are path-trust assumptions unchanged or documented?
- Are row ordinals, duplicate candidates, and result shapes still described
  consistently across Rust, Python, C, and Go?
- If a validation rule changes, is it a documented hardening fix rather than a
  silent compatibility break?