v2.14.0 — Brain scoping, honest degradation, and consolidation that finishes
Correctness release. The theme is silent failure: three of these bugs made the system look like it was working while it quietly was not — an empty-looking brain that held 10k neurons, recall that skipped reranking without saying so, and writes that landed but reported a timeout.
⚠️ Upgrade note — check your brain scopes
Rows are scoped by brain name (brain_id = "default"), but several call sites passed the brain record's UUID primary key. Reasoning mining was one such path, so its output landed in a scope recall never reads.
Before this release, a fetch by record id still returned those rows. #63 now brain-scopes every by-id fetch, so they become unreadable by any path until remapped.
-- Check first: a healthy install shows one scope per brain name, no UUIDs
SELECT brain_id, count() FROM neuron GROUP BY brain_id;
-- Remap if a UUID scope shows up
UPDATE neuron SET brain_id = "default" WHERE brain_id = "<uuid>";
UPDATE fiber SET brain_id = "default" WHERE brain_id = "<uuid>";
UPDATE synapse SET brain_id = "default" WHERE brain_id = "<uuid>";Security
- Cross-brain read via record id (#63) — every fetch-by-record-id did a bare
selectwith noWHERE brain_id, so a caller in brain A could read a record owned by brain B just by knowing its id. All six getters now scope to the brain, fail-closed, while staying pinned by id so the dashboard-perf property holds. - Id sanitisers consolidated (#63) — two paths bypassed the single
_to_surreal_idchoke point.
Fixed
- Brain-owned rows scoped by UUID instead of name (#97, #98) — reads reported an empty brain (grade F,
EMPTY_BRAIN) on a brain holding 10k+ neurons; writes were worse, with reasoning mining binding its whole job to a UUID scope.POST /brainnow pinsbrain_idto the name, closing the source rather than the symptoms. - Rerank degradation was silent (#97) — an unreachable reranker made recall fall back to raw spreading-activation order with only a log line. Recall now retries once and reports it:
rerank_degraded(MCP),rerank_degraded_warning(CLI). The reason string is sanitised so a remote endpoint cannot leak a token through it. - Consolidation aborted on large brains (#97) — per-strategy timeout had regressed to 120s (restored to 600s, total budget 3600s);
_lifecyclepulled 10k neurons with embeddings in one response, which SurrealDB dropped mid-transfer ([Errno 104] Connection reset by peer);_queryretried its reconnect once in the same instant, hitting the same reset. Verified live: a fullsmem consolidate --dry-runon an 11k-neuron brain now finishes in ~47s. - Writes reported a timeout after succeeding (#79, #99) — inline embedding ran unbounded in the write path, pushing
smem_rememberpast the 30s tool-call cap MCP hosts impose. Now bounded at 10s (SURREAL_MEMORY_INLINE_EMBED_TIMEOUT;<= 0restores the old behaviour). - Dangling synapses survived neuron deletion (#89) — the cascade filtered on
brain_id, but some write paths create synapses with a NULL one. SURREAL_MEMORY_BRAINignored when--brainomitted (#90, #98).- En-dash was not a clause boundary (#65).
Added
- Write-gate shadow/enforce modes (#93) —
off | shadow | enforcewith a per-intentauto_capture_mode, so the gate can be enforced on passive auto-captures while interactive writes stay in shadow. Defaults tooffwith a legacy fallback, so behaviour is unchanged until opted into. - Machine-noise denylist (#94) — empirically derived from real false-accepts (180×
Session activity:). - BGE-M3 embedding provider (#91) — 1024D, L2-normalised, with a zero-vector guard that raises rather than returning a fabricated
[0.0] * dimthat would silently poison top-k KNN. - Reranker Bearer auth (#92) — an empty key sends no header, so llamastash and llama.cpp are unaffected.
Changed
- Lint is pinned (#96) — an unpinned
ruffmade Lint a moving target; 0.16.0 turnedmainand every open PR red on the same day without a line of code changing.
Full changelog: v2.13.0...v2.14.0