mesh-llm-runtime-event-contracts
Dependency-leaf producer contracts for mesh-llm runtime events.
This crate defines the pure data model that the runtime-event system's
producers, ingress boundary, and reducer speak: fact families (what
happened), the wire-shape carriers each field rides on, process-local
identities, delivery classification, and the raw ABI mirror for events that
originate in native code. It is deliberately dependency-free of every
mesh-llm host/presentation crate — its only runtime dependency is uuid, so
nothing that consumes these types can create a dependency cycle back into
mesh-llm-host-runtime, mesh-llm-events, or the TUI. Downstream crates
build the actual event bus, reducer, and SSE wire encoding on top of these
contracts.
Event families
RuntimeFact is the top-level fact enum. Its 15 variants are each a
FamilyFact<K> pairing a family-specific K "kind" enum with a shared
FactData payload:
- Lifecycle families (
src/families/lifecycle.rs):NativeRuntimeEventKind,ModelPreparationEventKind,ModelLoadingEventKind,ModelAvailabilityEventKind,ModelUnloadingEventKind,StageTopologyEventKind,SessionEventKind. - Execution families (
src/families/execution.rs):RequestEventKind,PrefillEventKind,GenerationEventKind,KvRuntimeStateEventKind,ResourceHealthEventKind,DiagnosticEventKind,NodeAvailabilityEventKind,EventSystemHealthEventKind.
Each kind enum is generated by the crate-local event_family! macro
(src/lib.rs), which gives every variant a const ALL: &'static [Self] slice
and an as_str() stable wire id (e.g. ModelLoadFailed ->
"model_load_failed"). families::all_event_ids() concatenates every
family's ids into the full 184-id catalog. FactData (src/fact_data.rs)
carries the shared optional fields every fact may attach: ScopeIdentities
(model/topology/stage/session/request/device ids), a StateTransition, a
Progress, an Outcome, a ReasonCode (19 named codes plus a bounded
Unknown(UnknownReasonCode) escape hatch), a Duration, and bounded
NumericSummary / HumanSummary values, each length-capped (IDs at 256
bytes, state/summary keys smaller, human summaries at 512 bytes) so a
producer cannot smuggle unbounded payloads through the contract.
Envelope and identity
RuntimeEventEnvelope (src/carrier.rs) is the outer wire shape: schema
version, EventId, OperationScope, ProducerSource (Native / Rust /
Reconciled), Severity, wall-clock and process-monotonic timestamps, an
optional NativeSourceEnvelope, and the RuntimeFact payload. CarrierKind
enumerates all 19 named carrier fields (schema version, category/kind,
producer, severity, timestamps, sequence numbers, identities, state
transition, progress, outcome/reason, duration, bounded summaries) and which
of three locations — RuntimeEventEnvelope, NativeSourceEnvelope, or
FamilyFact — owns each one. This is the field-placement contract the
spec-pinned inventory below cross-checks.
src/identity.rs defines the process-local identity types: EventSequence
is the sole reducer ordering key (native sequence numbers are source data
only and carry no cross-process ordering or correlation promise);
ProcessInstanceId, OperationId, and ChildOperationId are UUID newtypes
minted by the uuid_identity! macro; and OperationScope distinguishes a
root operation (Root(OperationId)) from a child scoped under a root
(Child { root, child }). A request-root OperationId intentionally stores
the same UUID bytes as the logging request ID so the two can be correlated
without a mapping table.
Delivery classification
src/delivery/ classifies every fact into one of four DeliveryClass
values — Terminal, StateTransition, Progress, or Diagnostic — via
RuntimeFact::delivery_class(). The classification is a pure match over
each family's kind enum (delivery/lifecycle.rs for the seven lifecycle
families, delivery/execution.rs for the eight execution families); a
caller never supplies a delivery class itself, so classification cannot
drift per call site.
Ingress boundary
RuntimeEventIngress (src/ingress.rs) is the synchronous submission trait
producers call: try_submit(&self, fact: RuntimeFact) -> SubmitOutcome.
Implementations must return immediately without waiting for consumers.
SubmitOutcome reports what happened to the fact —
Accepted / Coalesced / DroppedProgress / DroppedDiagnostic /
RejectedShuttingDown / RejectedCapacity / RejectedCancelled /
TerminalDeliveryFailed — so a producer never
blocks on downstream delivery.
Native ABI mirror
src/native.rs mirrors the raw C struct the patched llama.cpp staged
runtime emits: NativeSourceEnvelope and its NativeEventCategory /
NativeEventKind / NativeEmitter / NativeProgressUnit /
NativeFailureCode / NativeStatus newtypes over the underlying integers.
The numeric_summary_0..3 fields are Option<u64> specifically because
their presence depends on the emitting native runtime's struct_size:
None means the native runtime predates that field (or was built without
it), not that the value is zero.
Spec-pinned inventory
inventory/runtime_events.toml is the crate's single normative catalog:
carriers, reason codes, native callback kinds, delivery/privacy classes,
projection profiles (and their allowed/forbidden projected keys), terminal-
outcome mappings, "same fact" rulings, and one [[families]] entry per
family (owning crates, source paths, producer symbols, authoritative
outcome, reducer effect, privacy class, and the migration task that
introduced it). It is pinned to a ratified spec by content hash:
runtime_events.toml's spec_sha256 field must equal the live SHA-256 of
.omo/specs/event-system.md, and tests/inventory_contract/metadata.rs
fails the suite if the two drift apart. tests/inventory_contract/coverage.rs
cross-checks the inventory's 137 spec-bullet expansions against
inventory/spec_manifest.json — itself produced from the same spec section
(8.1-8.15) by scripts/extract-runtime-event-spec-manifest.py — and
adversarial.rs / projection.rs round out the contract with tampered-input
and projection-key checks. scripts/generate-runtime-event-inventory.py
mechanically regenerates the TypeScript mirror at
fixtures/runtime_event_inventory.ts from the same TOML, so the amendment
procedure recorded in the inventory itself ("change the inventory, generated
manifest/docs/fixtures, exhaustive Rust tests, and schema-version review
atomically") is the intended way to add or change an event. The generator
requires Python 3.11 or newer and uses the standard-library TOML parser.
Fixtures
fixtures/runtime_events_v1/*.json (frames.json, cursors.json,
recovery.json) pin the /api/runtime/events/v1 SSE wire contract: event
names, required envelope/per-event keys, the projected-key allowlist, and
cursor/replay-gap framing. These fixtures are consumed from two independent
places so the contract cannot silently drift: mesh-llm-host-runtime's
api::routes::runtime_events::runtime_event_api_tests round-trip test in
Rust, and scripts/ci-windows-runtime-events-smoke.ps1, which drives a
composed Windows product binary and asserts its live SSE frames against the
same fixture files.
Dependencies
This is a dependency-leaf crate: its only runtime dependency is uuid
(the v4 feature, for the identity newtypes). Dev-dependencies (serde,
serde_json, sha2, toml) are used only by the tests/inventory_contract
suite to parse and hash-verify the inventory and spec manifest. It has zero
workspace-path dependencies by design, so nothing downstream — including
mesh-llm-host-runtime and the presentation layers that will eventually
consume these contracts — can create a cycle back into this crate.