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}