---
"@remnic/core": minor
---

Surface extraction-pipeline liveness (issue #2151). A daemon whose implicit extraction fails continuously can sit for months with a growing buffer while `/health` stays green and recall keeps serving — every individual failure is logged, but nothing aggregates them into a checkable status. A new `extractionLiveness` config block (`enabled` default true, `staleWindowMs` default 24h) drives a shared liveness verdict exposed three ways: the authenticated `/health` payload gains an `extraction` object (`lastExtractionAt`, buffered session/turn counts, oldest buffered turn age, `degraded`, `degradedReason`), `remnic doctor` gains an `extraction_liveness` check, and `remnic stats` prints the watermark, backlog, and verdict. `SmartBuffer.getBufferSnapshot()` enumerates buffered work across both the entries map and the legacy top-level turns array. Degradation follows the §22 error-vs-empty principle — it requires a non-empty buffer, so "nothing to extract" never reads as "extraction is broken" — and uses a half-open staleness window (§23). One aggregated WARN is logged per staleness window via an instance-scoped throttle rather than one line per failed attempt. The liveness logic lives in a new `extraction-liveness` module so the god-file line ratchets hold.

The `extraction` verdict is daemon-scoped: `/health` returns the same block for every namespace argument, pairing the global buffer with the newest last-extraction watermark across the root and every distinct namespace store. If namespace enumeration or any metadata read fails, the verdict reports an explicit unreadable watermark instead of publishing a surviving timestamp as fresh. The `watermarkScope` marker is `aggregate` on `/health`, in `remnic doctor`, and in `remnic stats`. An unreadable buffer is itself reported degraded (a pipeline fault, not an empty queue); a future or clock-skewed watermark is treated as stale so a real backlog stall is not hidden; and `extractionLiveness.staleWindowMs` must be a positive integer of milliseconds. A fractional, non-positive, or explicit-`null` value is rejected rather than floored or silently defaulted (only an absent key uses the 24h default).
