Skip to main content

tact_memory/server/
protocol.rs

1//! Versioned wire types shared by the remote client and reference server.
2
3use crate::{MemoryCandidate, MemoryKey, MemoryRecord};
4use const_format::concatcp;
5use serde::{Deserialize, Serialize};
6
7/// Header carrying the authenticated namespace on every request.
8pub const NAMESPACE_HEADER: &str = "x-tact-memory-namespace";
9/// Maximum UTF-8 bytes accepted in a namespace.
10pub const MAX_NAMESPACE_BYTES: usize = 128;
11/// Header carrying an opaque server-issued bookmark between requests.
12pub const BOOKMARK_HEADER: &str = "x-tact-memory-bookmark";
13/// Maximum UTF-8 bytes accepted in a bookmark.
14pub const MAX_BOOKMARK_BYTES: usize = 4 * 1024;
15
16/// Session-negotiation route.
17pub const SESSION_PATH: &str = concatcp!("v", crate::VERSION, "/session");
18/// Semantic scan route.
19pub const SCAN_PATH: &str = concatcp!("v", crate::VERSION, "/memories/scan");
20/// Record-read route.
21pub const READ_PATH: &str = concatcp!("v", crate::VERSION, "/memories/read");
22/// Visible-record listing route.
23pub const LIST_PATH: &str = concatcp!("v", crate::VERSION, "/memories/list");
24/// Direct mutation route.
25pub const PUT_PATH: &str = concatcp!("v", crate::VERSION, "/memories/put");
26/// Compare-and-swap deletion route.
27pub const DELETE_PATH: &str = concatcp!("v", crate::VERSION, "/memories/delete");
28/// Authoritative namespace snapshot route.
29pub const SYNC_PATH: &str = concatcp!("v", crate::VERSION, "/memories/sync");
30/// Paginated server snapshot export route.
31pub const EXPORT_PATH: &str = concatcp!("v", crate::VERSION, "/memories/export");
32/// Maximum records returned by one export page.
33pub const MAX_EXPORT_PAGE_RECORDS: usize = 128;
34
35/// Returns whether a namespace is non-empty, bounded, and URL/header safe.
36pub fn is_valid_namespace(namespace: &str) -> bool {
37    !namespace.is_empty()
38        && namespace.len() <= MAX_NAMESPACE_BYTES
39        && namespace
40            .bytes()
41            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
42}
43
44/// Returns whether an opaque bookmark is non-empty and bounded.
45pub fn is_valid_bookmark(bookmark: &str) -> bool {
46    !bookmark.is_empty() && bookmark.len() <= MAX_BOOKMARK_BYTES
47}
48
49/// Authorization role returned by session negotiation.
50#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
51#[serde(rename_all = "snake_case")]
52pub enum RemoteRole {
53    /// May scan, read, list, and export visible memories.
54    Reader,
55    /// May also mutate the credential's namespace.
56    Writer,
57}
58
59/// Session negotiation result.
60#[derive(Debug, Deserialize, Serialize)]
61pub struct SessionResponse {
62    /// Server wire protocol version.
63    pub protocol_version: u32,
64    /// Namespace bound to the presented credential.
65    pub namespace: String,
66    /// Operations authorized for the credential.
67    pub role: RemoteRole,
68}
69
70/// Request for semantic retrieval.
71#[derive(Debug, Deserialize, Serialize)]
72#[serde(deny_unknown_fields)]
73pub struct ScanRequest {
74    /// Search text, bounded by [`crate::MemoryLimits::query_bytes`].
75    pub query: String,
76    /// Maximum candidates requested from the server.
77    pub limit: usize,
78}
79
80/// Response to [`ScanRequest`].
81#[derive(Debug, Deserialize, Serialize)]
82pub struct ScanResponse {
83    /// Candidates in descending rank.
84    pub candidates: Vec<MemoryCandidate>,
85}
86
87/// Request to read unversioned IDs and versioned keys.
88#[derive(Debug, Deserialize, Serialize)]
89#[serde(deny_unknown_fields)]
90pub struct ReadRequest {
91    /// IDs in the authenticated namespace.
92    #[serde(default)]
93    pub ids: Vec<i64>,
94    /// Versioned keys, which may identify visible foreign namespaces.
95    #[serde(default)]
96    pub keys: Vec<MemoryKey>,
97}
98
99/// Response containing requested current records.
100#[derive(Debug, Deserialize, Serialize)]
101pub struct ReadResponse {
102    /// Existing, current requested records.
103    pub memories: Vec<MemoryRecord>,
104}
105
106/// Response containing a bounded deterministic window of visible records.
107#[derive(Debug, Deserialize, Serialize)]
108pub struct ListResponse {
109    /// At most [`crate::MemoryLimits::records`] records in deterministic store order.
110    pub memories: Vec<MemoryRecord>,
111}
112
113/// Request to insert or compare-and-swap replace a memory.
114#[derive(Debug, Deserialize, Serialize)]
115#[serde(deny_unknown_fields)]
116pub struct PutRequest {
117    /// New record content.
118    pub content: String,
119    /// Current key of the record to replace, or `None` to insert.
120    #[serde(default)]
121    pub replacement: Option<MemoryKey>,
122}
123
124/// Response to [`PutRequest`].
125#[derive(Debug, Deserialize, Serialize)]
126pub struct PutResponse {
127    /// Inserted or replaced current record.
128    pub memory: MemoryRecord,
129}
130
131/// Request to compare-and-swap delete a memory.
132#[derive(Debug, Deserialize, Serialize)]
133#[serde(deny_unknown_fields)]
134pub struct DeleteRequest {
135    /// Current key of the record to delete.
136    pub key: MemoryKey,
137}
138
139/// Authoritative full snapshot for one authenticated namespace.
140#[derive(Debug, Deserialize, Serialize)]
141#[serde(deny_unknown_fields)]
142pub struct SyncRequest {
143    /// Complete local snapshot; absent server records are deleted.
144    pub memories: Vec<MemoryRecord>,
145}
146
147/// Exclusive position in deterministic `(namespace, id)` export order.
148#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
149#[serde(deny_unknown_fields)]
150pub struct ExportCursor {
151    /// Namespace of the last returned record.
152    pub namespace: String,
153    /// ID of the last returned record.
154    pub id: i64,
155}
156
157/// Request for one deterministic snapshot export page.
158#[derive(Debug, Deserialize, Serialize)]
159#[serde(deny_unknown_fields)]
160pub struct ExportRequest {
161    /// `None` exports every namespace; an explicit list exports only those namespaces.
162    pub namespaces: Option<Vec<String>>,
163    /// Exclusive cursor returned by the preceding page.
164    #[serde(default)]
165    pub cursor: Option<ExportCursor>,
166    /// Requested page size, clamped to [`MAX_EXPORT_PAGE_RECORDS`].
167    pub limit: usize,
168}
169
170/// One deterministic snapshot export page.
171#[derive(Debug, Deserialize, Serialize)]
172pub struct ExportResponse {
173    /// Records strictly after the request cursor in `(namespace, id)` order.
174    pub memories: Vec<MemoryRecord>,
175    /// Cursor for another page, or `None` when the snapshot is exhausted.
176    pub next_cursor: Option<ExportCursor>,
177}
178
179/// Counts produced by applying an authoritative namespace snapshot.
180#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
181pub struct SyncReport {
182    /// Previously absent records inserted.
183    pub inserted: usize,
184    /// Existing records replaced by snapshot state.
185    pub replaced: usize,
186    /// Existing records already equal to snapshot state.
187    pub unchanged: usize,
188    /// Existing records absent from the snapshot and deleted.
189    pub deleted: usize,
190}
191
192/// Stable machine-readable remote failure category.
193#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
194#[serde(rename_all = "snake_case")]
195pub enum RemoteErrorCode {
196    /// Request shape or values are invalid.
197    BadRequest,
198    /// Credential is missing or invalid.
199    Unauthorized,
200    /// Credential does not authorize the operation.
201    Forbidden,
202    /// Requested namespace differs from the credential namespace.
203    NamespaceMismatch,
204    /// Client and server protocol versions differ.
205    UnsupportedProtocol,
206    /// Scan query exceeds its byte limit.
207    QueryTooLarge,
208    /// One memory exceeds its content byte limit.
209    ContentTooLarge,
210    /// Namespace record capacity is exhausted.
211    RecordCapacity,
212    /// Namespace aggregate content capacity is exhausted.
213    ContentCapacity,
214    /// Equivalent content already exists.
215    Duplicate,
216    /// Requested record does not exist.
217    NotFound,
218    /// Compare-and-swap version or snapshot state conflicts.
219    Conflict,
220    /// Service cannot currently complete the operation.
221    Unavailable,
222    /// Server failed without a safe client-visible detail.
223    Internal,
224}
225
226/// Error response returned for a failed protocol operation.
227#[derive(Debug, Deserialize, Serialize)]
228pub struct ErrorResponse {
229    /// Stable failure category.
230    pub code: RemoteErrorCode,
231}