polyc-facts 2026.9.0

Shared semantic-fold library: decode-to-fact functions reused by every consumer that reads the event log, so a payment receipt or a tool call means the same thing everywhere it's read.
//! `usage` event decode (#1579).
//!
//! Before this module existed, `usage` payload bytes decoded to meaning in
//! two independent places: the control plane's per-turn accounting
//! (`crates/control-plane/src/grpc/mod.rs`'s `decode_usage_payload`) and the
//! query engine's `usage` typed table (`crates/query/src/decode/usage.rs`).
//! Both called through the same underlying
//! [`polyc_proto::events_decode::try_decode_event_payload`], so the bytes
//! step was never really at risk of diverging — but the two call sites still
//! independently repeated the "which kind decodes to which fields" knowledge.
//! [`fold_usage_event`] is now the one place that knows it.
//!
//! What happens when decode FAILS is genuinely different policy, not fact,
//! between the two consumers: the query table skips the row and warns,
//! control-plane's accounting counts a corrupt entry as zero tokens and
//! warns. This module draws the same line [`crate::attribution`] draws —
//! the fold is the decode primitive; what a caller does with an `Err` stays
//! the caller's choice — so [`fold_usage_event`] returns a `Result`, never
//! swallowing the error itself.

use polyc_proto::proto::polychrome::events::v1::UsageEvent;

/// Decoded `usage` fact: the two token counts a `usage` event payload
/// carries.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct UsageFact {
    /// Prompt/input tokens consumed.
    pub input_tokens: u64,
    /// Completion/output tokens produced.
    pub output_tokens: u64,
}

impl From<UsageEvent> for UsageFact {
    fn from(event: UsageEvent) -> Self {
        Self {
            input_tokens: event.input_tokens,
            output_tokens: event.output_tokens,
        }
    }
}

/// Decode a `usage` event payload into its [`UsageFact`].
///
/// # Errors
///
/// Returns the underlying decode error on a structurally malformed
/// (non-empty) payload. An EMPTY payload decodes cleanly to
/// `UsageFact::default()` (proto3 elides all-default scalar fields) — a
/// genuine zero-usage turn, not a decode failure.
pub fn fold_usage_event(payload: &[u8]) -> Result<UsageFact, buffa::DecodeError> {
    polyc_proto::events_decode::try_decode_event_payload::<UsageEvent>(payload).map(UsageFact::from)
}

#[cfg(test)]
mod tests {
    #![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]

    use buffa::Message as _;

    use super::*;

    #[test]
    fn round_trips_a_usage_payload() {
        let event = UsageEvent {
            input_tokens: 9,
            output_tokens: 3,
            __buffa_unknown_fields: buffa::UnknownFields::default(),
        };
        let bytes = event.encode_to_vec();
        let fact = fold_usage_event(&bytes).expect("decode usage");
        assert_eq!(fact.input_tokens, 9);
        assert_eq!(fact.output_tokens, 3);
    }

    #[test]
    fn empty_payload_decodes_to_zero_zero() {
        let fact = fold_usage_event(&[]).expect("empty payload decodes");
        assert_eq!(fact, UsageFact::default());
    }

    #[test]
    fn garbage_bytes_return_an_error() {
        assert!(fold_usage_event(&[0xFF, 0xFE, 0xFD]).is_err());
    }
}