Repository navigation
v0.13.0
khive v0.13.0
Minor release. Five public Rust types change in ways that break source compatibility for code that constructs them directly or matches them exhaustively; they are listed under Upgrading with what to change. The events protocol version is now 6, so clients and daemons must be upgraded together.
The release adds knowledge.lint with transactional fixes, screened delivery of mail from outside senders (opt-in with KHIVE_EMAIL_EXTERNAL_SENDERS) with comm.withheld_body for withheld originals, a per-event missed-schedule policy for schedule.remind and schedule.schedule, configurable Tier 1 eligibility for kkernel kg review, the !topmost:<authserv-id> trust anchor, and per-stage search profiling with KHIVE_SEARCH_PROFILE. The filesystem blob store's default free-space floor now scales with its volume.
Each entry below links the pull request that made the change. Every pull request in the release is listed at the end.
Upgrading from 0.12.0
khive_channel::ChannelEnvelopeaddsinbound_delivery, the host-verified routing for an inbound message (never serialized). Direct struct literals must set it, usually toNone. (#5395)khive_runtime::events_split::EventsRequest::CountEventsandCountEventsGroupedaddoperator_read_budget_secs, andEventsResponse::Erroraddsread_failure. Direct constructors and exhaustive destructurings of these variants must account for them. (#5402)khive_runtime::RuntimeErroradds theLintFixvariant, which keeps a lint fix's typed cause and its confirmed receipts. The enum is not#[non_exhaustive], so downstream exhaustive matches must handle it. (#5386)khive_channel_email::config::TrustAnchoradds theTopmostWithAuthservIdvariant for!topmost:<authserv-id>. The enum is not#[non_exhaustive], so downstream exhaustive matches must handle it. (#5408)- Clients and daemons speak events protocol version 6; upgrade them together. (#5402)
FsBlobStore::DEFAULT_FLOOR_BYTESand bothis_meaningful_queryfunctions remain for one release, deprecated; see Changed for their replacements. (#5410, #5346)
Added
knowledge.lintinspects the caller-visible graph against the thirteen built-in hygiene rules and returns typed violations, with statistics taken before any fix. Requested fixes soft-delete unchanged dangling edges, propose questions about duplicate groups and mark unchanged superseded entities deprecated, each checked inside its own transaction; the receipt keeps earlier committed fixes when a later one conflicts or fails. The gate classifies the verb as Write. Custom rules (knowledge.lint_config) remain deferred. (#5386)- With
KHIVE_EMAIL_EXTERNAL_SENDERSset, authenticated mail from outside senders passes deterministic screening and routes to a verified thread owner, a configured recipient alias or a fallback actor. Delivered messages read back markedorigin: externalandtrust: untrustedwith the rule IDs that matched. A screening rule can withhold the body: the message is delivered with a rule-ID placeholder, and itsquarantine-originalattachment owns the original MIME bytes, which the recipient actor or the configured maintainer reads throughcomm.withheld_body. A reply asking to stop records an address suppression before the message is acknowledged. With the variable unset, startup and mail handling are unchanged. (#5395) schedule.remindandschedule.scheduleacceptmisfire_policy(skiporcatch_up_once) andmax_lateness(positive seconds, required withcatch_up_once). An overduecatch_up_onceevent dispatches its latest due occurrence once when that occurrence is no older thanmax_lateness; older occurrences are recorded as missed and never dispatched. Events without a policy keep the existing skip behaviour and theKHIVE_FIRE_GRACE_SECSwindow. (#5376)kkernel kg reviewreads Tier 1 eligibility from the same rules snapshot it validates with. An optional[tier]section configures the operation kinds, link relations, mutable entity fields and minimum link weight eligible for Tier 1. Empty lists narrow eligibility, and judgment relations, destructive operations, existing-edge updates, low weights and error findings still require Tier 2.khive-rule-evaluatornow depends onkhive-changeset. (#5396)comm.withheld_bodyreads the original RFC 5322 bytes of a delivered withheld external message, base64-encoded with their size. Only the message's recipient actor and the configured maintainer may read it; any other caller is refused before the original is read, and an id that is not a delivered withheld message is refused. Every call writes an audit event naming the caller, the message id and the outcome. The response never carries a content reference. (#5419)EntityPosteriors::fold_evidenceapplies weightedsupportsandrefutesevidence to a claim's posterior, rejecting invalid relations and weights before changing values or eviction order. Either signed zero is a mutation-free no-op. (#5115)- Inbound email follows one disposition order. A malformed or oversized message is quarantined first. A subject of the form
unsubscribe <token>whose token verifies records an outboundrecipient_keysuppression entry (sourcemailto_token) and the message is neither delivered nor quarantined. With external senders configured, an inbound suppression entry matching the authenticated sender address or its exact domain quarantines the message with reasonblocked. (#5373) KHIVE_EMAIL_AUTHSERV_IDaccepts!topmost:<authserv-id>(TrustAnchor::TopmostWithAuthservId). Only the topmostAuthentication-Resultsheader is considered, as with!topmost-no-authserv-id, and it is trusted when it carries the configuredauthserv-idor none. This covers Exchange Online, which stampsmx.microsoft.com 1on its own header for some senders and noauthserv-idfor others. An empty or malformed id after the prefix fails configuration. Adding the variant to the publicTrustAnchorenum is a breaking change for exhaustive matches. (#5408)- With
KHIVE_SEARCH_PROFILEset, each search the coordinator runs across configured backends writes one JSON line per stage to stderr (backend,merge,fanout,finalize,hydrateandsearch), grouped by a call id that a nested search shares with its outer call. Abackendline names the backend, its hit count and its outcome (ok,errorortimeout). With the variable unset nothing is written and no stage timer reads the clock. (#5414)
Changed
- With no configured floor, the filesystem blob store's free-space floor is 2% of the volume holding the blob root, never less than 5 GiB, instead of a fixed 100 GB. A configured
floor_bytesis used as given, including0.FsBlobStore::DEFAULT_FLOOR_BYTESremains, deprecated. (#5410) - Event counts keep the caller's request-read budget (
KHIVE_REQUEST_READ_TIMEOUT_SECS) instead of the fixed 10-second RPC timeout, and typed read and admission timeouts cross the events wire intact. The events protocol version is now 6, so clients and daemons must be upgraded together. (#5402) - Configurable graph rules share the pure
khive-rule-evaluatorcrate betweenkg validateandkg commit, using captured NDJSON inputs and explicit citation time. Runtime and offline checks share the canonical endpoint table and matcher. (#5126) - Name the distinct query admission policies
khive_text::is_meaningful_text_queryandkhive_pack_memory::scoring::is_meaningful_recall_query, preserving their existing behavior. Bothis_meaningful_queryfunctions remain as deprecated compatibility wrappers for one release; callers should use the replacement names. (#5346)
Fixed
- A daemon that supervises an events daemon now holds each request until that child's socket answers for the first time, instead of dispatching while the child is still starting. A write sent in that window could commit its domain change and then report a failed audit commit. If the socket has not answered within 10 seconds of startup, requests are refused before dispatch with
events_daemon_unavailable,retryable: trueanddomain_disposition: not_committed, naming the socket, until it answers. Probe frames are answered without waiting. (#5344) - The email poll loop no longer holds its IMAP page without bound. Every cause that holds a page, a
comm.ingestrefusal or a quarantined message's original that cannot be stored, now counts toward one bound per message: the message is recorded as quarantined on its 60th failed poll while the store refuses it as retryable, and on its 5th for every other cause. If the original cannot be stored, or the record that references it is refused, the record is written without it and names the reason.comm.healthreportsconsecutive_held_polls,held_messages,last_held_atandlast_quarantined_after_hold_atper channel. (#5348) - External inbound email no longer routes on an unproven thread UUID. A sender-asserted
X-Khive-Thread-IDthat names a thread routes only when that thread holds an outbound email whose Message-ID verifies under the configured mailbox domains, the same proof theIn-Reply-To/ReferencesMessage-ID path already requires. A UUID naming any other thread falls through to the recipient aliases and then the external inbound actor. Maintainer mail keeps its existing resolution. (#5418) - A new quarantine record's
comm.ingestreceipt now reports whether the record kept its original:quarantine_original_retained(a boolean) and, when it is false,quarantine_original_not_retained_reasonnaming the stored reason. A non-quarantine message and a deduplicated acknowledgement carry neither. The success heartbeat gainsoriginals_not_retained(a running total of records written without their original, withoriginal_not_retained_reason),original_retention_state(retainedorfailing),notices_failed(a running total) andnotice_config_error(cleared by a later success that omits it); a failure heartbeat refuses all of them.comm.healthreports each per channel,nullfor a writer that does not report them and for quarantine-only entries. (#5415) - Brain verbs report storage timeouts and admission refusals as typed storage errors instead of internal errors, so callers keep the retryable classification. (#5412)
- A note update whose model reindex fails after the commit returns the typed committed degradation error, naming each failed model and stage, instead of reporting success. (#5151)
- A verified mailto opt-out received while the email host has no comm store is kept for retry instead of being consumed as already recorded. (#5399)
- With
KHIVE_RECALL_PROFILE=1, each memory recall labels its stage records with its own call ID and writes exactly onetotalrecord on every exit, including empty results, errors and cancellation. (#5352) - Events store directory refusals name the database path and the ownership, permission or location remedy. (#5356)
Performance
- Reindex writes each page of notes or entities to the full-text index as one batch, keeping successful documents when one in the page is refused. (#5163)
- Vamana insertion reuses pooled visited-node scratch instead of allocating it per insert. (#5366)
Documentation
- Accept ADR-200, the external email policy: sender scope, suppression, unsubscribe and screened inbound delivery. (#5379)
- Accept ADR-106 Amendment H, a per-event missed-schedule policy in which
catch_up_oncerequires an explicitmax_lateness. (#5367) - Accept ADR-140, the context graph payload budget rule; ADR-089 now points to it. (#5349)
- Accept the ADR-102 and ADR-046 2026-10-14 amendments: a staged change-set lands through one atomic
apply_changesetverb, and canonical change-set proposals land through the same path. Neither is implemented yet. (#5358, #5389)
All pull requests (81)
Features
- feat(comm): read withheld originals through an audited comm.withheld_body (#5419)
- feat(comm): disclose quarantine original retention and notice delivery (#5415)
- feat(email): trust the topmost Authentication-Results with a shared authserv-id (#5408)
- feat(rules): configure shared change-set tier predicate (#5396)
- feat(email): screen and route external inbound mail (#5395)
- feat(knowledge): inspect and repair graph hygiene (#5386)
- feat(schedule): add bounded per-event catch-up execution (#5376)
- feat(email): order inbound disposition and accept mailto unsubscribe (#5373)
- feat(rules): share pure graph validation (#5126)
- feat(brain): fold weighted evidence into claim posteriors (#5115)
Fixes
- fix(comm): external thread-UUID routing requires a verified outbound email (#5418)
- fix(brain): keep storage errors typed through persistence (#5412)
- fix(blob): scale the default free-space floor with the volume (#5410)
- fix(events): retain caller count read budgets (#5402)
- fix(email): retain opt-outs when the comm store is missing (#5399)
- fix(runtime): explain events store trust refusals (#5356)
- fix(memory): scope recall profiling to each call (#5352)
- fix(channel): bound every hold of an inbound page (#5348)
- fix(daemon): hold requests until the supervised events daemon answers (#5344)
- fix(runtime): preserve note update post-commit failures (#5151)
Performance
- perf(search): opt-in stage timing for searches across backends (#5414)
- perf(vamana): reuse visited scratch during insertion (#5366)
Documentation
- docs(adr): ADR-056 amendment, operator notices for lost messages and originals (#5411)
- docs(adr): propose ADR-200 Amendment 1, withheld originals and external routing (#5403)
- docs: link the hosted service from the README (#5397)
- docs(adr): accept the ADR-102 and ADR-046 2026-10-14 amendments (#5389)
- docs(adr): accept ADR-200 external email policy (#5379)
- docs(adr): accept ADR-106 Amendment H with an explicit catch-up bound (#5367)
- docs(adr): propose optional receipt cursor pagination (#5365)
- docs(adr): land change-sets through one atomic verb (ADR-102, ADR-046 amendments) (#5358)
- docs(adr): accept ADR-140 and point ADR-089 at its budget rule (#5349)
Tests
- test(runtime): pin the unsubscribe token format to a shared test vector (#5416)
- test(kkernel): cover combined BibTeX import acceptance (#5383)
- test(memory): hold ANN publication for missing session proof (#5368)
Refactoring
- refactor(fold): separate builtin objective tests (#5401)
- refactor(brain-core): move profile tests beside source (#5398)
- refactor(tests): separate private foundation and adapter modules (#5394)
- refactor(storage): separate error policy tests (#5393)
- refactor(runtime): group daemon process, drain and metrics tests (#5392)
- refactor(vamana): separate private configuration and graph tests (#5390)
- refactor(hnsw): separate integration test concerns (#5388)
- refactor(runtime): separate private lifecycle test modules (#5387)
- refactor(tests): separate private unit-test concerns (#5382)
- refactor(wire): separate error and version unit tests (#5380)
- refactor(score): separate three unit-test modules (#5378)
- refactor(runtime): separate four unit-test modules (#5377)
- refactor(knowledge): share raw score ordering (#5374)
- refactor(kg): use shared raw weight ordering (#5371)
- refactor(curation): share endpoint-only edge rewire statement (#5369)
- refactor(runtime): separate retrieval unit tests (#5355)
- refactor(memory): separate ANN epoch and score contracts (#5353)
- refactor(query): name distinct admission policies (#5346)
- refactor(query): extract language dispatch unit tests (#5343)
- refactor(brain-core): extract section and tuning unit tests (#5342)
- refactor(session): extract export and vocabulary unit tests (#5341)
- refactor(types): extract scalar and identity unit tests (#5340)
- refactor(git): extract environment and parameter unit tests (#5339)
- refactor(code): extract adapter and source boundary tests (#5338)
- refactor(fold): extract residual unit test modules (#5337)
- refactor(kg): extract diagnostics and vocabulary tests (#5336)
- refactor(runtime): share savepoint statement execution (#5335)
- refactor(memory): reuse minimum score parsing (#5334)
- refactor(runtime): share merge storage error mapping (#5333)
- refactor(retrieval): retire unreachable engine replay integration (#5332)
- refactor(runtime): share stale snapshot conflict construction (#5331)
- refactor(runtime): extract objective registry tests (#5330)
- refactor(runtime): share note and stream validation checks (#5325)
- refactor(brain): extract inline test modules (#5248)
- refactor(objectives): share recency decay arithmetic (#5224)
- refactor(runtime): share note-field secret scans (#5202)
- refactor(mcp): reuse shared attachment count validation (#5178)
- refactor(brain): reuse shared SQL row accessors (#5177)
- refactor(db): share Windows handle-path queries (#5176)
- refactor(db): share synchronous merge tombstone execution (#5175)
- refactor(kkernel): batch full-text index backfill (#5163)
- refactor(brain): group event count admission totals (#5150)
- refactor(score): centralize total ordering for raw scores (#5142)
Build, CI and release
- chore(release): 0.13.0 (#5420)
- chore(deps): update rustls to 0.23.46 in khive-merge (#5384)
- ci: pin the contract harness to Python 3.12 and update rustls to 0.23.46 (#5381)
- fix(ci): separate contract harness dependency setup (#5357)
Full list of changes: v0.12.0...v0.13.0