Skip to content

v0.13.0

Choose a tag to compare

@ohdearquant ohdearquant released this 15 Oct 12:35
· 683 commits to main since this release
9fb0757

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::ChannelEnvelope adds inbound_delivery, the host-verified routing for an inbound message (never serialized). Direct struct literals must set it, usually to None. (#5395)
  • khive_runtime::events_split::EventsRequest::CountEvents and CountEventsGrouped add operator_read_budget_secs, and EventsResponse::Error adds read_failure. Direct constructors and exhaustive destructurings of these variants must account for them. (#5402)
  • khive_runtime::RuntimeError adds the LintFix variant, 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::TrustAnchor adds the TopmostWithAuthservId variant 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_BYTES and both is_meaningful_query functions remain for one release, deprecated; see Changed for their replacements. (#5410, #5346)

Added

  • knowledge.lint inspects 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_SENDERS set, 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 marked origin: external and trust: untrusted with the rule IDs that matched. A screening rule can withhold the body: the message is delivered with a rule-ID placeholder, and its quarantine-original attachment owns the original MIME bytes, which the recipient actor or the configured maintainer reads through comm.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.remind and schedule.schedule accept misfire_policy (skip or catch_up_once) and max_lateness (positive seconds, required with catch_up_once). An overdue catch_up_once event dispatches its latest due occurrence once when that occurrence is no older than max_lateness; older occurrences are recorded as missed and never dispatched. Events without a policy keep the existing skip behaviour and the KHIVE_FIRE_GRACE_SECS window. (#5376)
  • kkernel kg review reads 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-evaluator now depends on khive-changeset. (#5396)
  • comm.withheld_body reads 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_evidence applies weighted supports and refutes evidence 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 outbound recipient_key suppression entry (source mailto_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 reason blocked. (#5373)
  • KHIVE_EMAIL_AUTHSERV_ID accepts !topmost:<authserv-id> (TrustAnchor::TopmostWithAuthservId). Only the topmost Authentication-Results header is considered, as with !topmost-no-authserv-id, and it is trusted when it carries the configured authserv-id or none. This covers Exchange Online, which stamps mx.microsoft.com 1 on its own header for some senders and no authserv-id for others. An empty or malformed id after the prefix fails configuration. Adding the variant to the public TrustAnchor enum is a breaking change for exhaustive matches. (#5408)
  • With KHIVE_SEARCH_PROFILE set, each search the coordinator runs across configured backends writes one JSON line per stage to stderr (backend, merge, fanout, finalize, hydrate and search), grouped by a call id that a nested search shares with its outer call. A backend line names the backend, its hit count and its outcome (ok, error or timeout). 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_bytes is used as given, including 0. FsBlobStore::DEFAULT_FLOOR_BYTES remains, 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-evaluator crate between kg validate and kg 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_query and khive_pack_memory::scoring::is_meaningful_recall_query, preserving their existing behavior. Both is_meaningful_query functions 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: true and domain_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.ingest refusal 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.health reports consecutive_held_polls, held_messages, last_held_at and last_quarantined_after_hold_at per channel. (#5348)
  • External inbound email no longer routes on an unproven thread UUID. A sender-asserted X-Khive-Thread-ID that names a thread routes only when that thread holds an outbound email whose Message-ID verifies under the configured mailbox domains, the same proof the In-Reply-To/References Message-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.ingest receipt now reports whether the record kept its original: quarantine_original_retained (a boolean) and, when it is false, quarantine_original_not_retained_reason naming the stored reason. A non-quarantine message and a deduplicated acknowledgement carry neither. The success heartbeat gains originals_not_retained (a running total of records written without their original, with original_not_retained_reason), original_retention_state (retained or failing), notices_failed (a running total) and notice_config_error (cleared by a later success that omits it); a failure heartbeat refuses all of them. comm.health reports each per channel, null for 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 one total record 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_once requires an explicit max_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_changeset verb, 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