Skip to main content

kindling_types/
list.rs

1//! Types for the exhaustive, deterministically-paginated observation list
2//! (`POST /v1/observations/list`).
3//!
4//! Distinct from [`crate::RetrieveOptions`] / [`crate::RetrieveResult`], which
5//! are *ranked* top-K retrieval. The list API enumerates the **full** set of
6//! observations matching a `(kind, scope, time-range)` filter, in the stable
7//! `(ts ASC, id ASC)` order the store guarantees, via a keyset cursor — so a
8//! consumer can compute exact counts / set-differences over every matching row.
9
10use serde::{Deserialize, Serialize};
11
12#[cfg(feature = "ts-rs")]
13use ts_rs::TS;
14
15use crate::common::{ScopeIds, Timestamp};
16use crate::observation::{Observation, ObservationKind};
17
18/// Filter + pagination for an observation list request.
19///
20/// Time bounds are **half-open**: `since` is inclusive, `until` is exclusive, so
21/// two adjacent range polls `[t0, t1)` + `[t1, t2)` never double-count the
22/// boundary. `task_id` is intentionally **not** a filter dimension (it is carried
23/// only for provenance) — `scope_ids.task_id` is ignored if set.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
26#[serde(rename_all = "camelCase")]
27pub struct ListObservationsRequest {
28    /// Scope filter (session / repo / agent / user).
29    #[serde(default)]
30    pub scope_ids: ScopeIds,
31    /// Restrict to these observation kinds. Empty (or omitted) = all kinds.
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub kinds: Vec<ObservationKind>,
34    /// Inclusive lower bound on `ts` (epoch ms).
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
37    pub since: Option<Timestamp>,
38    /// Exclusive upper bound on `ts` (epoch ms).
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
41    pub until: Option<Timestamp>,
42    /// Max rows per page. Server-clamped to `[1, 1000]`; default 100.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    #[cfg_attr(feature = "ts-rs", ts(optional))]
45    pub limit: Option<u32>,
46    /// Opaque cursor from a prior response's `nextCursor`. Treat as a token; do
47    /// not parse or construct it.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    #[cfg_attr(feature = "ts-rs", ts(optional))]
50    pub cursor: Option<String>,
51    /// Include redacted rows. Default `false`. A redacted row keeps its `kind`,
52    /// scope and provenance but its `content` is `[redacted]`.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    #[cfg_attr(feature = "ts-rs", ts(optional))]
55    pub include_redacted: Option<bool>,
56}
57
58/// One page of observations in stable `(ts ASC, id ASC)` order.
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
61#[serde(rename_all = "camelCase")]
62pub struct ListObservationsResult {
63    /// The matching observations for this page.
64    pub observations: Vec<Observation>,
65    /// Present iff more rows remain — pass it back as `cursor` to fetch the next
66    /// page. **Absent means enumeration is complete** (the completeness signal a
67    /// consumer needs for an exact count / set-difference).
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    #[cfg_attr(feature = "ts-rs", ts(optional))]
70    pub next_cursor: Option<String>,
71}