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

Add corpus watermarks (issue #2149, detection half) — a cheap, comparable fingerprint of each daemon's active-memory corpus so an HA pair behind an active/backup VIP can detect silent corpus divergence (a primary at ~190k files and a backup at ~340k, discovered only by manual audit, becomes a same-day signal). The new `corpus-watermark.ts` exports the `CorpusWatermark` shape plus a pure, filesystem-free `digestPartitionCensus()` — sha256 over the DETERMINISTICALLY sorted per-`<tier>:<category>/<YYYY-MM-DD>` file-count census (a census fingerprint, not a content hash; AGENTS.md pattern 26) — and `computeCorpusWatermark()`, which sources paths from the cheap `StorageManager.collectActiveMemoryPaths()` (hot) and `collectColdMemoryPaths()` (cold) scans (no frontmatter parse) and `stat`s only the newest HOT partition's files for `newestWriteAt`, so it survives a 100k+ file corpus without the cost of `readAllMemories()`. The census is tier-aware: demoted-but-reachable cold memories are counted with `cold:` bucket keys so a hot/cold split shows in the digest instead of two replicas reading "converged" while their cold tiers differ; freshness stays scoped to the hot tier. Health and `remnic doctor` resolve their namespace set through one shared `resolveCorpusNamespaceRoots()` helper (config-driven, so it still works when the namespace catalog is opted out, with live catalog names unioned in) so the two surfaces cannot drift and silently omit a tenant. The watermark is exposed to AUTHENTICATED callers on `GET /engram/v1/health` as `corpus: CorpusWatermark[]`, filtered to the presenting token's namespaces (a namespace-scoped bearer sees only its own tenants; an operator token sees the fleet) and served through a bounded 60s TTL + single-flight cache so routine readiness/HA polling never triggers a full corpus rescan per request. `remnic doctor` reports the same watermark in its `corpus_watermark` check. Peer polling and divergence alerting are a follow-up PR, so this check does no peer comparison yet; it reports `warn` only when a namespace scan or enumeration fails (never a false-empty `ok`). The doctor summarizer lives in a new `operator-doctor-corpus.ts` sibling module because `operator-toolkit.ts` is at its structural-ratchet ceiling.
