docs(release): the 10.4.0 entry, the index-health concept doc, and the API surfaces — written from the tree, not the plan
This commit is contained in:
parent
b9ba50fbec
commit
8cced871a0
8 changed files with 454 additions and 63 deletions
84
RELEASES.md
84
RELEASES.md
|
|
@ -31,6 +31,90 @@ is sometimes cited as a 7.x removal — those methods never existed on 7.x; the
|
|||
|
||||
---
|
||||
|
||||
## v10.4.0 — 2026-08-25 (the health report has a name)
|
||||
|
||||
Three related cures, one root cause: an index deciding whether it could be trusted
|
||||
by sampling itself instead of by exact accounting. This release replaces every
|
||||
sampled self-probe with ledger-derived truth, and a read against an unhealthy index
|
||||
now refuses loudly instead of guessing.
|
||||
|
||||
- **The canonical count ledger.** Storage now tracks two scalars per family
|
||||
(nouns/verbs) on the write path: the user-facing `counted` total — unchanged,
|
||||
still what `getNounCount()` / `getVerbCount()` return — and a new ALL-visibility
|
||||
`all` total covering every tier, the real denominator a derived index's own
|
||||
coverage math needs. The unfiltered storage-level `totalCount` returned by
|
||||
`getNouns()` / `getVerbs()` is now this unclamped ALL scalar; previously it could
|
||||
only ever move up (`Math.max(scalar, scanned)`), so an inflated counter could
|
||||
never self-correct. A delete that cannot prove the record it removed actually
|
||||
existed (no canonical read, no prior image available) no longer decrements on
|
||||
faith — it marks the ledger `suspect` (narrated once per session) instead of
|
||||
silently drifting, and the next `repairIndex()` clears the flag with a real
|
||||
recount.
|
||||
- **One contract for a throwing health probe.** A provider's `validateInvariants()`
|
||||
is documented to never throw — but if one does anyway (a bug, a transient fault),
|
||||
it is now read the same way everywhere: `heal: 'none'`, the error named in the
|
||||
report, never synthesized into a rebuild trigger and never swallowed into "looks
|
||||
fine." A flaky check can no longer buy itself a rebuild. `repairIndex()`'s
|
||||
per-family receipt also gains `missing` (an exact count plus a capped id sample),
|
||||
`rebuilt` (a full rebuild ran, vs. an incremental heal), and `reason`.
|
||||
- **The named health report; reads refuse instead of rebuilding.** Any index
|
||||
provider may now expose a synchronous, O(1) `healthReport()` — composed from the
|
||||
provider's own exact ledgers, never a sample — and this is the one signal
|
||||
Brainy's read gate trusts. The first-query lazy-build path is gone: `brain.init()`
|
||||
now runs every needed rebuild to completion before it returns, always, regardless
|
||||
of dataset size. A read that lands on a provider whose health report says it
|
||||
isn't serving throws a typed error instead of triggering a rebuild mid-query —
|
||||
`GraphIndexNotReadyError`, `MetadataIndexNotReadyError`, or
|
||||
`VectorIndexNotReadyError` (all exported from `@soulcraft/brainy`), naming the
|
||||
reasons. `repairIndex({ rebuild: ['metadata' | 'graph' | 'vector'] | 'all' })` is
|
||||
the new explicit operator door: it rebuilds the named family unconditionally, no
|
||||
health check consulted — reach for it when you have independent reason to
|
||||
distrust a family regardless of what it self-reports. Bare `repairIndex()` is
|
||||
unchanged in spirit: report-driven, heals only what its own checks say needs it.
|
||||
- New concept doc: [Index Health](docs/concepts/index-health.md) walks the whole
|
||||
story from a consumer's side — degraded-but-serving vs. not-ready, what
|
||||
`repairIndex()` checks and heals per family, what `suspect` counts mean.
|
||||
|
||||
**Nothing to change to adopt this.** No API removed, no signature narrowed —
|
||||
`repairIndex()` gains an optional options bag and its return value gains fields,
|
||||
both additive. The honest notes: if your code ever relied on a `find()` against a
|
||||
cold/not-yet-built index quietly triggering a rebuild and returning results a beat
|
||||
later, that behavior is gone — it now throws one of the three typed
|
||||
`*NotReadyError` classes instead (catch them if you need to distinguish "not ready
|
||||
yet" from "no results"). And `disableAutoRebuild: true` no longer defers index
|
||||
construction to the first query — a needed rebuild always runs at `open()` now;
|
||||
the flag has no effect on timing. Full manual control still lives in
|
||||
`repairIndex({ rebuild: [...] })`.
|
||||
|
||||
- **Crash-reopen catchup.** After an unclean shutdown, the metadata index now
|
||||
folds the exact fact window it missed — `find()` serves every acked write on
|
||||
reopen, closing the gap where canonical reads and counts recovered a
|
||||
crash-window write but the index kept serving its pre-crash state until the
|
||||
next full rebuild. Related root-cause fixed alongside: `close()` never
|
||||
stamped the index watermarks (only `flush()` did), so a close without a
|
||||
prior flush caused a needless full rescan verdict on the next open.
|
||||
- **Relation rows are live in the metadata index.** Previously verb rows
|
||||
entered the metadata index only during a rebuild — so a rebuilt store's
|
||||
relation postings went stale from the first `relate()` after it. Relations
|
||||
are now posted and retracted on the live write path (relate / unrelate /
|
||||
updateRelation / remove's cascade, and their `transact()` forms), in the
|
||||
same commit as the graph leg.
|
||||
- **The metadata rebuild is online.** `rebuild()` for the metadata family no
|
||||
longer clears and rebuilds in place (reads went empty for the duration): it
|
||||
builds a complete replacement beside the serving index, mirrors concurrent
|
||||
writes to both, swaps atomically, and persists once after the swap. Reads
|
||||
never observe a partial index. `repairIndex({ rebuild: ['metadata'] })` uses
|
||||
it automatically.
|
||||
- **A broken accelerator install can never read as "not installed."** The
|
||||
auto-detection free pass now requires the resolution error to name the
|
||||
accelerator package itself, exactly — a missing platform-binary sibling
|
||||
package, an inner file path, or a dependency failure is a broken install and
|
||||
`init()` throws loudly. And a plugin that declines activation is narrated on
|
||||
the always-on log channel, so `silent: true` can no longer hide a fallback
|
||||
to the default engines.
|
||||
|
||||
---
|
||||
|
||||
## v10.3.1 — 2026-08-18 (the fold that behaves)
|
||||
|
||||
Three recovery cures from one production first-boot incident (a brain's first
|
||||
|
|
|
|||
Reference in a new issue