mempill_core/application/dto.rs
1//! Public DTOs — the stable API surface consumed by all bindings.
2//!
3//! Domain types from `mempill-types` are referenced here but raw internal engine types
4//! never cross this boundary; callers only see these structs.
5
6use mempill_types::{
7 AgentId, BeliefProjection, Cardinality, ClaimRef, Confidence, Criticality, Disposition,
8 HistoryEntryStatus, LedgerEntry, ProvenanceLabel, ValidTime,
9};
10
11// ── INGEST CLAIM ──────────────────────────────────────────────────────────────
12
13/// Public write request. Maps to domain Claim at the application boundary.
14#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
15pub struct IngestClaimRequest {
16 /// The agent performing the write.
17 pub agent_id: AgentId,
18 /// Opaque key — the entity the claim is about (e.g. `"acme:ceo"`). mempill does
19 /// **not** perform entity resolution; you must use the same `subject` on write and
20 /// read. Adopt a canonical key convention and apply it consistently across both paths.
21 pub subject: String,
22 /// Opaque key — the property being asserted (e.g. `"held_by"`). Like [`Self::subject`],
23 /// it is matched verbatim; the engine cannot reconcile differently-keyed facts for you.
24 pub predicate: String,
25 /// The JSON value being asserted.
26 pub value: serde_json::Value,
27 /// Required; no default imposed here — gateway enforces ModelDerived default for model output.
28 pub provenance: ProvenanceLabel,
29 /// Caller-supplied cardinality hint; the adjudication gate may override or contest it.
30 pub cardinality: Cardinality,
31 /// None = unknown; fallback to tx_time ordering.
32 pub valid_time: Option<ValidTime>,
33 /// Confidence in the value and valid-time assertion (0.0–1.0 each).
34 pub confidence: Confidence,
35 /// Criticality class for this claim.
36 pub criticality: Criticality,
37 /// Lineage for ModelDerived claims.
38 pub derived_from: Vec<ClaimRef>,
39}
40
41/// Response from a successful claim ingest.
42#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
43pub struct IngestClaimResponse {
44 /// Stable UUID reference to the committed claim.
45 pub claim_ref: ClaimRef,
46 /// The engine's disposition for this write.
47 pub disposition: Disposition,
48 /// Populated when disposition is Contested or PendingConflict.
49 pub contested_with: Vec<ClaimRef>,
50}
51
52// ── QUERY MEMORY ──────────────────────────────────────────────────────────────
53
54/// Request to retrieve the current belief for a (subject, predicate) subject-line.
55#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
56pub struct QueryMemoryRequest {
57 /// The agent whose memory is queried.
58 pub agent_id: AgentId,
59 /// The subject of the query.
60 pub subject: String,
61 /// The predicate of the query.
62 pub predicate: String,
63 /// Optional: query as of a specific transaction time (bi-temporal as-of query).
64 ///
65 /// When set, only claims whose transaction time is at or before this instant are
66 /// considered (the transaction-time axis). Controls assertion visibility as well.
67 pub as_of_tx_time: Option<chrono::DateTime<chrono::Utc>>,
68 /// Optional: select the belief valid at this specific valid-time instant (valid-time axis).
69 ///
70 /// When set, after the transaction-time visibility filter is applied, the fold
71 /// narrows the result to the single claim whose valid-time window contains this
72 /// instant (D2 independence rule: tx-time filter first, then valid-time selection).
73 ///
74 /// When `None`, the existing backward-compatible behaviour is preserved: the
75 /// `as_of_tx_time` (or `now`) is used as the valid-time selection instant.
76 #[serde(default)]
77 pub valid_at: Option<chrono::DateTime<chrono::Utc>>,
78}
79
80/// Response from a memory query — the canonical belief projection.
81#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
82pub struct QueryMemoryResponse {
83 /// Canonical fold result; computed at read time, never persisted.
84 pub belief: BeliefProjection,
85}
86
87// ── RECONCILE ─────────────────────────────────────────────────────────────────
88
89/// Request to reconcile one or more subject lines.
90#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
91pub struct ReconcileRequest {
92 /// The agent whose subject lines are reconciled.
93 pub agent_id: AgentId,
94 /// Subject lines to reconcile. Empty = reconcile all subject lines for agent_id.
95 pub subject_lines: Vec<(String, String)>, // (subject, predicate) pairs
96}
97
98/// Response from a reconciliation pass.
99#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
100pub struct ReconcileResponse {
101 /// Per-claim disposition outcomes from the reconciliation pass.
102 pub outcomes: Vec<(ClaimRef, Disposition)>,
103 /// Number of subject lines that required oracle escalation.
104 pub oracle_escalations: u32,
105}
106
107// ── QUERY HISTORY ────────────────────────────────────────────────────────────
108
109/// Request to retrieve the full history timeline for a (subject, predicate) subject-line.
110///
111/// Returns all claims ever written to the line, ordered by the canonical ordering key
112/// (valid_time_start when confidence ≥ threshold, else tx_time). Each entry is tagged
113/// `Current` or `Superseded` based on the same canonical fold that powers `query_memory`.
114#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
115pub struct QueryHistoryRequest {
116 /// The agent whose history is queried.
117 pub agent_id: AgentId,
118 /// The subject of the history query.
119 pub subject: String,
120 /// The predicate of the history query.
121 pub predicate: String,
122}
123
124/// One slot in the history timeline for a subject-line.
125///
126/// `status` is derived from `is_live` in the canonical fold — the `Current` entry is
127/// exactly the claim that `recall` / `query_memory` would return as primary.
128#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
129pub struct HistoryEntry {
130 /// Stable reference to the underlying claim (UUID).
131 pub claim_ref: ClaimRef,
132 /// The asserted value for this claim.
133 pub value: serde_json::Value,
134 /// Start of the valid-time window, or `None` if unknown.
135 pub valid_from: Option<chrono::DateTime<chrono::Utc>>,
136 /// Effective end of the slot: equals the successor's canonical ordering key,
137 /// or `None` for the open-ended current slot.
138 pub valid_until: Option<chrono::DateTime<chrono::Utc>>,
139 /// Whether this claim is the live belief or has been superseded.
140 pub status: HistoryEntryStatus,
141 /// Human-readable provenance label (e.g. `"External/UserAsserted"`).
142 pub provenance: String,
143 /// Confidence in the claim's value (0.0–1.0).
144 pub value_confidence: f32,
145}
146
147/// Response from `query_history` — the full ordered timeline for a subject-line.
148#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
149pub struct QueryHistoryResponse {
150 /// All claims for the subject-line, ordered by canonical ordering key (oldest first).
151 pub entries: Vec<HistoryEntry>,
152}
153
154impl QueryHistoryResponse {
155 /// Convenience: returns the single `Current` entry, if any.
156 pub fn current(&self) -> Option<&HistoryEntry> {
157 self.entries.iter().find(|e| e.status == HistoryEntryStatus::Current)
158 }
159}
160
161// ── QUERY SUBJECT ─────────────────────────────────────────────────────────────
162
163/// Request to retrieve the resolved belief for every predicate stored under a subject.
164///
165/// Returns one [`SubjectFactEntry`] per distinct predicate that has at least one claim
166/// satisfying the `as_of_tx_time` cutoff. Each entry is the same fold result that
167/// `query_memory` would produce for that `(subject, predicate)` pair under the same
168/// `valid_at` / `as_of_tx_time` — the existing fold/disposition logic is reused, not
169/// reimplemented.
170#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
171pub struct QuerySubjectRequest {
172 /// The agent whose memory is queried.
173 pub agent_id: AgentId,
174 /// The subject for which all predicate beliefs are returned.
175 pub subject: String,
176 /// Optional: query as of a specific valid-time instant (valid-time axis).
177 ///
178 /// When set, the fold selects the claim whose valid-time window contains this instant.
179 /// When `None`, the backward-compatible behaviour is used (as_of / now drives both axes).
180 #[serde(default)]
181 pub valid_at: Option<chrono::DateTime<chrono::Utc>>,
182 /// Optional: query as of a specific transaction time (bi-temporal tx-time axis).
183 ///
184 /// When set, only claims whose `tx_time <= as_of_tx_time` are visible.
185 /// When `None`, the full current view is used.
186 #[serde(default)]
187 pub as_of_tx_time: Option<chrono::DateTime<chrono::Utc>>,
188}
189
190/// One per-predicate entry in a [`QuerySubjectResponse`].
191///
192/// Mirrors the shape that `query_memory` + `enrich_query_memory` would produce for a
193/// single `(subject, predicate)` pair. The field names match the API contract exactly
194/// so Python callers can read them as dict keys without additional mapping.
195#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
196pub struct SubjectFactEntry {
197 /// The predicate this entry describes.
198 pub predicate: String,
199 /// The resolved value string, or `None` when the status is `NoBelief`.
200 pub value: Option<String>,
201 /// Resolved belief status: `"Resolved"`, `"Contested"`, `"NoBelief"`, or `"TimingUncertain"`.
202 pub status: String,
203 /// Start of the valid-time window rendered at recorded precision (e.g. `"2020-03"`).
204 /// `None` when the start endpoint is unknown.
205 pub valid_from_display: Option<String>,
206 /// End of the valid-time window rendered at recorded precision.
207 /// `None` when the end endpoint is unknown / open-ended.
208 pub valid_until_display: Option<String>,
209 /// Human-readable provenance label, or `"none"` when there is no primary belief.
210 pub provenance: String,
211 /// Stable UUID reference to the primary claim, or `None` when there is no primary.
212 pub claim_ref: Option<String>,
213 /// Value confidence of the primary claim (0.0–1.0), or `None` when absent.
214 pub conf: Option<f32>,
215}
216
217/// Response from a `query_subject` call — one entry per distinct predicate.
218///
219/// Entries are sorted by `predicate` (lexicographic) for stable, deterministic output.
220#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
221pub struct QuerySubjectResponse {
222 /// Per-predicate fold results, sorted by predicate.
223 pub entries: Vec<SubjectFactEntry>,
224}
225
226// ── AUDIT QUERY ───────────────────────────────────────────────────────────────
227
228/// Request to query the audit ledger.
229#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
230pub struct AuditQueryRequest {
231 /// The agent whose audit ledger is queried.
232 pub agent_id: AgentId,
233 /// None = load full ledger for agent_id.
234 pub claim_ref: Option<ClaimRef>,
235 /// Filter to entries recorded at or after this transaction time.
236 pub from_tx_time: Option<chrono::DateTime<chrono::Utc>>,
237 /// Maximum number of entries to return.
238 pub limit: usize,
239}
240
241/// Response from an audit ledger query.
242#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
243pub struct AuditQueryResponse {
244 /// The matching audit ledger entries.
245 pub entries: Vec<LedgerEntry>,
246}