pub struct ObsEvent {
pub pc_id: String,
pub at: DateTime<Utc>,
pub kind: String,
pub source: String,
pub event_record_id: Option<String>,
pub payload: Value,
}Expand description
One observability event published to obs.<pc_id>.
The kind field is a free-form string — vocabulary lives at
the consumer (backend projector decides filtering / coloring;
SPA decides chip labels). Established kinds at #246 land:
logon/logoff— Security log 4624 / 4634boot/shutdown— System log 12 / 13 (kernel-general)unexpected_shutdown— System log 41sleep/resume— System log 42 / 107agent_started/agent_self_update— agent-emitted (later)diagnostic— kanade logs collect bundles (#219)
New kinds can be added without a wire change; the backend projector stores whatever string the agent sends and the SPA surfaces it.
Fields§
§pc_id: StringPC reporting the event. Routing key on the subject side
(obs.<pc_id>) and primary scope of the SPA timeline view.
at: DateTime<Utc>Wall-clock instant of the event as known to the SOURCE
(e.g. Windows Event Log’s TimeCreated), NOT the moment
the agent published it. The timeline must reflect when
things happened on the box, not when the projector heard
about them — the two can differ by minutes when the agent
is catching up from outbox after a broker outage.
kind: StringWhat kind of event this is — the SPA’s filter chip and the backend projector’s coloring key. See the doc comment on this struct for the vocabulary at landing.
source: StringWhere this event came from. Format <scheme>:<detail>
(e.g. winlog:System, winlog:Security, agent:internal,
kanade:logs_collect). Two roles:
- Distinguishes events from different sources that might
share an
event_record_idnamespace. - Lets the SPA filter “show me only winlog events” without needing a separate enum.
event_record_id: Option<String>Stable per-source unique identifier — e.g. EventRecordID
from the Windows Event Log. Combined with pc_id and
source it forms the dedup key, so agent re-sends (under
watermark drift, outbox replay, etc.) are harmless.
None for sources that have no natural unique id (e.g.
agent-emitted milestones where the only candidate is the
at timestamp + kind, which the backend can synthesize
from those fields if needed).
#[serde(default)] so an agent publisher that has no id
to emit can omit the field entirely; serde fills None
rather than refusing the message. Without this, agent
versions that always send the field can deserialize but
future ones that drop it on null cases would silently
land in the warn-log → projector drop path.
payload: ValueFree-form per-kind details. The wire stays narrow
(pc_id, at, kind, source, event_record_id) and
the per-kind shape lives here:
logon:{ "user": "...", "logon_type": 2 }boot: typicallynullor{}— the bare presence is the eventdiagnostic:{ "bucket": "OBJECT_DIAGNOSTICS", "key": "..." }— pointer to the actual log blob
Backend projector stores this as TEXT (the JSON representation); SPA renders it kind-aware.
#[serde(default)] so a publisher emitting a bare-presence
event can omit the field entirely (serde fills
Value::Null) rather than being forced to write
"payload": null on every line.