precinct 0.9.1

Approximate nearest-neighbor search over region embeddings
Documentation
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). The 0.x series is
unstable: minor bumps may break the public API.

## [Unreleased]

## [0.9.0] - 2026-07-09

### Added

- Added a `store_reopen_diagnostics` example that prints first snapshot-search
  cost with persisted region-index sidecars present versus after deleting
  sidecars and forcing source-segment rebuilds.
- `examples/updatable_store` demonstrates the optional `store::UpdatableIndex`
  path: checkpoint, delete, reopen, and the region query families over the
  recovered store.
- Added `store::SnapshotIndex`, a read-only checkpoint view that opens the
  segstore manifest and queries persisted `RegionIndex` sidecars before falling
  back to one source-region segment when a sidecar is missing or stale.
- `store::UpdatableIndex` now exposes the region query algebra over sealed
  segments plus the unflushed buffer: containment, strict and soft subsumption,
  subsumees, overlap, nearest-region, and exhaustive variants.
- The `heyting_queries` example now uses heyting 0.5 conformal answer sets to
  calibrate held-out hypernym coverage over trained boxes.

### Changed

- `store::UpdatableIndex` now keys its in-memory per-segment `RegionIndex` cache
  by segstore's stable segment ids instead of `Arc` pointers, and prunes stale
  cache entries when compaction/reclaim changes the segment set.
- Store writer searches now build the temporary writer-buffer `RegionIndex` from
  the buffer slice instead of cloning buffered regions first.
- The optional `store` stack now requires `segstore` 0.5, `durability` 0.7.1,
  and `vicinity` 0.11.0, using `segstore::DefaultStore` for source-region
  segments and `segstore::SidecarEnvelope` for checked region-index sidecar
  framing.

### Fixed

- Store sidecar envelopes now include the `segstore` segment id, so a copied or
  misnamed `RegionIndex` sidecar is rejected and rebuilt instead of being
  accepted for a different segment.
- `store::UpdatableIndex::{compact, compact_tiers, reclaim}` now persist sidecars
  for newly merged segments immediately after segstore checkpoints them, instead
  of waiting for the next search to rebuild and write the sidecar lazily.

## [0.8.5] - 2026-06-30

### Added

- The `store` feature now persists per-segment `RegionIndex` sidecars and loads
  them on restart when the build recipe and live id set still match. On
  `benches/store.rs` (`N=8_000`, `DIM=64`, `FLUSH=1_000`), cold restart search
  with sidecars measured `[10.150 ms 10.350 ms 10.553 ms]` versus
  `[1.7605 s 1.7678 s 1.7750 s]` when sidecars were missing and every segment
  had to rebuild.

### Changed

- The `store` feature now requires `segstore = "0.4"`, `postcard`, and
  `vicinity >= 0.10.5`.

## [0.8.4] - 2026-06-28

### Added

- `store::UpdatableIndex::extend(regions)`: bulk ingest that syncs the write-ahead
  log once per batch instead of once per region (every region's dimension is
  validated before any is ingested). ~2.6x faster than a loop of `add` for a
  corpus load on a real filesystem (bench `ingest_fs`: 12.3ms vs 4.8ms / 4000
  regions).

### Changed

- The `store` feature now requires `segstore = "0.3"`; the internal `merge_segments`
  takes `&[&Segment]` (segstore 0.3's by-reference signature).

## [0.8.3] - 2026-06-27

### Changed

- A `delete` now invalidates only the cached index of the segment that holds the
  id, not the whole cache, so one delete no longer forces every segment to
  rebuild on the next query.

## [0.8.2] - 2026-06-27

### Added

- `store::UpdatableIndex::compact_tiers()`: one round of size-tiered compaction
  (merge similarly-sized segments), keeping segment count bounded without a full
  `compact()`.

## [0.8.1] - 2026-06-27

### Added

- `store::UpdatableIndex::reclaim(min_live_ratio)` and `space_amplification()`
  (via the new `Store::live_len`): cheap tombstone reclamation, merging only the
  delete-heavy segments instead of a full compaction.

## [0.8.0] - 2026-06-27

### Added

- `RegionIndex::subsumers_soft(query, min_prob)`: soft subsumption, returning
  regions with `entailment_prob(query) >= min_prob` ranked by probability.
  Trained region embeddings (Gumbel boxes) nest *softly* -- a child's center
  falls inside its parent but its full box pokes out -- so strict `subsumers` is
  0% on real trained WordNet boxes while the soft form (and membership) recover
  the is-a ancestor 98%. `min_prob = 1.0` reduces to strict containment.
- `examples/wordnet_boxes`: in-domain validation on subsume's trained WordNet box
  checkpoint, reporting membership / soft / strict ancestor recall (98/98/0%).

## [0.7.0] - 2026-06-27

### Added

- `Ellipsoid` region type (axis-aligned, per-axis semi-axes): the anisotropic
  member of the region family. Exact `contains`, `log_volume`, and a
  Newton-iterated exact point-to-surface `distance_to_point`; region-to-region
  predicates use the bounding box as a documented approximation. Implements
  `Region`, so it works in `RegionIndex` like `AxisBox` / `Ball`.
- A containment-recall regression test on a realistic far-centered hierarchy
  (big general boxes + small nested boxes): the lift recovers enclosing regions
  at >95% recall vs the exhaustive ground truth.

## [0.6.0] - 2026-06-27

Completes the region query algebra: beyond containment, the serving interface
now covers overlap, region-to-region similarity, and the box scoring primitives.

### Added

- `RegionIndex::overlapping(region)` / `overlapping_exhaustive` -- the overlap
  query (regions intersecting a query region, the conjunction primitive).
- `RegionIndex::nearest_region(region, k)` -- region-to-region nearest neighbor
  (a concept's nearest concepts), by center distance.
- `Region::overlaps_region` (intersection predicate), `Region::log_volume`
  (generality), and `Region::entailment_prob` (soft subsumption probability
  `vol(self ∩ other) / vol(other)`, the box-lattice conditional; exact for boxes,
  approximate for balls). Implemented for `AxisBox` and `Ball`.

### Breaking

- `Region` requires three more methods: `overlaps_region`, `log_volume`,
  `entailment_prob`. Custom `Region` implementations must add them; `AxisBox` and
  `Ball` are covered.

## [0.5.0] - 2026-06-27

precinct becomes a high-dimensional index for regions-as-objects (the serving
layer for trained region embeddings), answering three query families instead of
one. See `docs/design/region-index.md`.

### Added

- `RegionIndex::containing(point)` -- membership query, now backed by a
  *power-distance lift*: each region is indexed by a vector whose ranking
  respects extent, so an enclosing region is found even when its center is far
  from the query (the case a center-only index misses).
- `RegionIndex::subsumers(region)` / `subsumees(region)` -- region-to-region
  subsumption retrieval (a concept's hypernyms / hyponyms), with
  `subsumers_exhaustive` for ground truth.
- `Region::bounding_ball()` and `Region::contains_region()` on the trait, with
  `AxisBox` and `Ball` implementations.

### Changed

- `RegionIndex` now keeps two HNSW graphs: a center graph for `search` (nearest)
  and a lift graph for `containing` / `subsumers`. Nearest recall is unchanged
  (GloVe recall@10 92.1% at 10x); routing nearest through the lift would have
  cost ~4 points, so the queries use separate indexes (~2x graph memory).

### Breaking

- `Region` requires two new methods, `bounding_ball` and `contains_region`. Any
  custom `Region` implementation must add them; `AxisBox` and `Ball` are covered.

## [0.4.0] - 2026-06-27

### Added

- `examples/glove_concepts`: a recall benchmark on real high-dimensional concept
  regions (top-50K GloVe-6B-50d vectors clustered into 5,000 bounding boxes via
  `clump`). recall@10 92.1% (10x over-retrieve), 99.3% (50x). Fetch with
  `scripts/fetch_glove.sh`.
- `examples/geo_regions`: nearest-region search over real geographic boxes (177
  Natural Earth country bounding boxes). Shows the surface-vs-center distinction
  (a South Pacific point resolves to Chile). Fetch with
  `scripts/fetch_natural_earth.sh`.

### Changed

- `store::UpdatableIndex` now caches each segment's `RegionIndex` by the
  segment's stable `Arc` identity (via segstore 0.2), so a mutation rebuilds only
  the new or changed segments instead of the whole corpus on the next query.
- Requires `segstore` 0.2 (only affects the optional `store` feature; the on-disk
  store format changed, so a `store` index written by 0.3.x is not read by 0.4.0).

## [0.3.1] - 2026-06-26

### Fixed

- `store::UpdatableIndex` caches the per-segment `RegionIndex` and rebuilds it
  only on mutation (add/delete/compact), instead of rebuilding every segment on
  every query.
- `store::UpdatableIndex::add` now returns an error for a region whose dimension
  does not match the index, rather than silently dropping it from every rebuild.

### Changed

- `store` docs no longer claim an "exact" cross-segment merge. The underlying
  HNSW search is approximate, so the merged result is approximate.

## [0.3.0] - 2026-06-26

### Added

- Optional `serde` feature: `Serialize`/`Deserialize` derives on `AxisBox`.
- Optional `store` feature: `store::UpdatableIndex`, an updatable, durable
  multi-segment region-ANN index over axis-aligned boxes, backed by
  [`segstore`]https://crates.io/crates/segstore. A per-segment `RegionIndex` is
  built from the live regions of each segment and searched, then the per-segment
  top-k are merged (exact). Incremental add/delete plus write-ahead log,
  checkpoint, compaction, and crash recovery. Opt-in; the default build does not
  depend on segstore.

## [0.2.0]

Initial documented release.