yantrikdb-protocol 0.1.0

Wire protocol codec for YantrikDB — binary frames, opcodes, MessagePack messages, Tokio codec
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
//! Typed message payloads for each wire protocol command.
//!
//! All messages serialize to MessagePack via serde.
//! The server and client use these to construct and parse frame payloads.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// ── Auth ──────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthRequest {
    pub token: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthOkResponse {
    pub database: String,
    pub database_id: i64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthFailResponse {
    pub reason: String,
}

// ── Database ──────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct SelectDbRequest {
    pub name: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateDbRequest {
    pub name: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DbOkResponse {
    pub name: String,
    pub message: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ListDbResponse {
    pub databases: Vec<DatabaseInfo>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DatabaseInfo {
    pub id: i64,
    pub name: String,
    pub created_at: String,
}

// ── Remember ──────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct RememberRequest {
    pub text: String,
    #[serde(default = "default_memory_type")]
    pub memory_type: String,
    #[serde(default = "default_importance")]
    pub importance: f64,
    #[serde(default)]
    pub valence: f64,
    #[serde(default = "default_half_life")]
    pub half_life: f64,
    #[serde(default)]
    pub metadata: serde_json::Value,
    #[serde(default)]
    pub namespace: String,
    #[serde(default = "default_certainty")]
    pub certainty: f64,
    #[serde(default)]
    pub domain: String,
    #[serde(default = "default_source")]
    pub source: String,
    #[serde(default)]
    pub emotional_state: Option<String>,
    /// Client-provided embedding vector. If None, server computes it.
    #[serde(default)]
    pub embedding: Option<Vec<f32>>,
}

fn default_memory_type() -> String {
    "semantic".into()
}
fn default_importance() -> f64 {
    0.5
}
fn default_half_life() -> f64 {
    168.0
} // 7 days in hours
fn default_certainty() -> f64 {
    1.0
}
fn default_source() -> String {
    "user".into()
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RememberOkResponse {
    pub rid: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RememberBatchRequest {
    pub memories: Vec<RememberRequest>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RememberBatchOkResponse {
    pub rids: Vec<String>,
}

// ── Recall ────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct RecallRequest {
    pub query: String,
    #[serde(default = "default_top_k")]
    pub top_k: usize,
    #[serde(default)]
    pub memory_type: Option<String>,
    #[serde(default)]
    pub include_consolidated: bool,
    #[serde(default = "default_true")]
    pub expand_entities: bool,
    #[serde(default)]
    pub namespace: Option<String>,
    #[serde(default)]
    pub domain: Option<String>,
    #[serde(default)]
    pub source: Option<String>,
    /// Client-provided query embedding. If None, server computes it.
    #[serde(default)]
    pub query_embedding: Option<Vec<f32>>,
}

fn default_top_k() -> usize {
    10
}
fn default_true() -> bool {
    true
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RecallResultMsg {
    pub rid: String,
    pub text: String,
    pub memory_type: String,
    pub score: f64,
    pub importance: f64,
    pub created_at: f64,
    pub why_retrieved: Vec<String>,
    #[serde(default)]
    pub metadata: serde_json::Value,
    #[serde(default)]
    pub namespace: String,
    #[serde(default)]
    pub domain: String,
    #[serde(default)]
    pub source: String,
    #[serde(default)]
    pub certainty: f64,
    #[serde(default)]
    pub valence: f64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RecallEndMsg {
    pub total: usize,
    pub confidence: f64,
}

// ── Graph ─────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct RelateRequest {
    pub entity: String,
    pub target: String,
    pub relationship: String,
    #[serde(default = "default_weight")]
    pub weight: f64,
}

fn default_weight() -> f64 {
    1.0
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RelateOkResponse {
    pub edge_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EdgesRequest {
    pub entity: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EdgesResultMsg {
    pub edges: Vec<EdgeMsg>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EdgeMsg {
    pub edge_id: String,
    pub src: String,
    pub dst: String,
    pub rel_type: String,
    pub weight: f64,
}

// ── Forget ────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct ForgetRequest {
    pub rid: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ForgetOkResponse {
    pub rid: String,
    pub found: bool,
}

// ── Session ───────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct SessionStartRequest {
    #[serde(default = "default_namespace")]
    pub namespace: String,
    #[serde(default)]
    pub client_id: String,
    #[serde(default)]
    pub metadata: serde_json::Value,
}

fn default_namespace() -> String {
    "default".into()
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SessionEndRequest {
    pub session_id: String,
    #[serde(default)]
    pub summary: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SessionOkResponse {
    pub session_id: String,
    #[serde(default)]
    pub duration_secs: Option<f64>,
    #[serde(default)]
    pub memory_count: Option<i64>,
    #[serde(default)]
    pub topics: Option<Vec<String>>,
}

// ── Think ─────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct ThinkRequest {
    #[serde(default = "default_true")]
    pub run_consolidation: bool,
    #[serde(default = "default_true")]
    pub run_conflict_scan: bool,
    #[serde(default)]
    pub run_pattern_mining: bool,
    #[serde(default)]
    pub run_personality: bool,
    #[serde(default = "default_consolidation_limit")]
    pub consolidation_limit: usize,
}

fn default_consolidation_limit() -> usize {
    50
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ThinkResultMsg {
    pub consolidation_count: usize,
    pub conflicts_found: usize,
    pub patterns_new: usize,
    pub patterns_updated: usize,
    pub personality_updated: bool,
    pub duration_ms: u64,
    pub triggers: Vec<TriggerMsg>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TriggerMsg {
    pub trigger_type: String,
    pub reason: String,
    pub urgency: f64,
    pub source_rids: Vec<String>,
    pub suggested_action: String,
}

// ── Subscribe / Events ────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct SubscribeRequest {
    pub events: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct UnsubscribeRequest {
    pub events: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EventMsg {
    pub event_type: String,
    pub data: serde_json::Value,
}

// ── Conflicts ─────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct ConflictsRequest {
    #[serde(default)]
    pub status: Option<String>,
    #[serde(default)]
    pub conflict_type: Option<String>,
    #[serde(default)]
    pub entity: Option<String>,
    #[serde(default = "default_limit")]
    pub limit: usize,
}

fn default_limit() -> usize {
    50
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ResolveRequest {
    pub conflict_id: String,
    pub strategy: String,
    #[serde(default)]
    pub winner_rid: Option<String>,
    #[serde(default)]
    pub new_text: Option<String>,
    #[serde(default)]
    pub resolution_note: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ConflictMsg {
    pub conflict_id: String,
    pub conflict_type: String,
    pub priority: String,
    pub status: String,
    pub memory_a: String,
    pub memory_b: String,
    pub entity: Option<String>,
    pub detection_reason: String,
    pub detected_at: f64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ConflictResultMsg {
    pub conflicts: Vec<ConflictMsg>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ResolveOkResponse {
    pub conflict_id: String,
    pub strategy: String,
}

// ── Info ──────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct PersonalityResultMsg {
    pub traits: Vec<PersonalityTraitMsg>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PersonalityTraitMsg {
    pub name: String,
    pub score: f64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct StatsResultMsg {
    pub active_memories: i64,
    pub consolidated_memories: i64,
    pub tombstoned_memories: i64,
    pub edges: i64,
    pub entities: i64,
    pub operations: i64,
    pub open_conflicts: i64,
    pub pending_triggers: i64,
}

// ── Error ─────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    pub code: u16,
    pub message: String,
    #[serde(default)]
    pub details: Option<HashMap<String, serde_json::Value>>,
}

// ── Cluster / Replication ─────────────────────────────────────────

/// Initial peer-to-peer handshake.
#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterHello {
    pub node_id: u32,
    pub role: String, // "voter" | "read_replica" | "witness"
    pub current_term: u64,
    pub cluster_secret: String,
    pub advertise_addr: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterHelloOk {
    pub node_id: u32,
    pub role: String,
    pub current_term: u64,
    pub leader_id: Option<u32>,
}

/// Request operations from a peer's oplog since a watermark.
#[derive(Debug, Serialize, Deserialize)]
pub struct OplogPullRequest {
    /// Database name to pull from. Defaults to "default" if missing.
    #[serde(default = "default_db_name")]
    pub database: String,
    pub since_hlc: Option<Vec<u8>>, // 16-byte HLC timestamp, None for "from beginning"
    pub since_op_id: Option<String>,
    pub limit: usize,                  // max ops per batch
    pub exclude_actor: Option<String>, // skip ops from this actor (avoid loops)
}

fn default_db_name() -> String {
    "default".into()
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OplogPullResult {
    pub ops: Vec<OplogEntryWire>,
    pub has_more: bool,
}

/// Wire-friendly representation of an oplog entry.
#[derive(Debug, Serialize, Deserialize)]
pub struct OplogEntryWire {
    pub op_id: String,
    pub op_type: String,
    pub timestamp: f64,
    pub target_rid: Option<String>,
    pub payload: serde_json::Value,
    pub actor_id: String,
    pub hlc: Vec<u8>,
    pub embedding_hash: Option<Vec<u8>>,
    pub origin_actor: String,
}

/// Push ops to a peer (used by primary → secondary push).
#[derive(Debug, Serialize, Deserialize)]
pub struct OplogPushRequest {
    #[serde(default = "default_db_name")]
    pub database: String,
    pub ops: Vec<OplogEntryWire>,
}

/// Request the list of databases on a peer (so we can create matching ones).
#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterDatabaseListRequest {}

#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterDatabaseListResponse {
    pub databases: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OplogPushOkResponse {
    pub applied: usize,
    pub last_hlc: Vec<u8>,
    pub last_op_id: String,
}

/// Heartbeat from leader to followers.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatMsg {
    pub term: u64,
    pub leader_id: u32,
    pub leader_last_hlc: Vec<u8>,
    pub leader_last_op_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HeartbeatAckMsg {
    pub term: u64,
    pub follower_id: u32,
    pub follower_role: String,
    pub follower_last_hlc: Vec<u8>,
    pub follower_last_op_id: String,
    pub lag_seconds: f64,
}

/// Vote request from a candidate during election.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestVoteMsg {
    pub term: u64,
    pub candidate_id: u32,
    pub last_log_hlc: Vec<u8>,
    pub last_log_op_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct VoteResponseMsg {
    pub term: u64,
    pub voter_id: u32,
    pub granted: bool,
    pub reason: Option<String>,
}

/// Cluster overview request.
#[derive(Debug, Serialize, Deserialize)]
pub struct ClusterStatusResultMsg {
    pub current_term: u64,
    pub leader_id: Option<u32>,
    pub self_id: u32,
    pub self_role: String,
    pub peers: Vec<PeerStatusMsg>,
    pub quorum_size: usize,
    pub healthy: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PeerStatusMsg {
    pub node_id: u32,
    pub addr: String,
    pub role: String,
    pub reachable: bool,
    pub current_term: u64,
    pub last_seen_secs_ago: f64,
    pub lag_seconds: f64,
}

// ── Error codes ───────────────────────────────────────────────────

pub mod error_codes {
    pub const AUTH_REQUIRED: u16 = 1000;
    pub const AUTH_INVALID: u16 = 1001;
    pub const DB_NOT_FOUND: u16 = 2000;
    pub const DB_ALREADY_EXISTS: u16 = 2001;
    pub const MEMORY_NOT_FOUND: u16 = 3000;
    pub const INVALID_PAYLOAD: u16 = 4000;
    pub const INTERNAL_ERROR: u16 = 5000;
    pub const EMBEDDING_ERROR: u16 = 5001;
    // Cluster errors
    pub const READONLY_NODE: u16 = 6000; // Can't write to read replica
    pub const NOT_LEADER: u16 = 6001; // Try the current leader instead
    pub const NO_QUORUM: u16 = 6002; // Cluster lost quorum
    pub const CLUSTER_SECRET_MISMATCH: u16 = 6003;
    pub const PEER_TERM_MISMATCH: u16 = 6004;
}