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