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
//! Per-conversation turn serialization for the SM context engine (#1309).
//!
//! Why: the file-based [`SmContextEngine`](crate::core::sm::context::SmContextEngine)
//! is opened fresh per turn and persists by ATOMIC WHOLE-FILE replace with NO
//! merge. Two concurrent turns for the SAME `conv_id` each load the state into
//! their own in-memory conversation, append their round, and save — so the second
//! save clobbers the first and SILENTLY LOSES a round, even though that round's
//! reply was already returned to the caller (a data-integrity bug, #1309). This
//! became reachable once the SM was wired into the async `coordinator/chat`
//! endpoint, which can drive multiple in-flight turns for one `conv_id`.
//! Serializing turns per `conv_id` closes the read-modify-write race with the
//! lightest correct mechanism — a per-`conv_id` async lock held across
//! open → record → save — mirroring the per-palace `write_mutex` precedent in
//! `trusty_common::memory_core::retrieval::handle`.
//! What: [`ConvLocks`] is a small registry of per-`conv_id` [`tokio::sync::Mutex`]es.
//! [`ConvLocks::acquire`] hands back an owned guard the turn holds for its whole
//! duration; concurrent turns for the SAME id wait for it, turns for DIFFERENT ids
//! never block each other. The registry stores [`Weak`] handles and prunes dead
//! entries on every miss, so it stays bounded by the number of concurrently-active
//! conversations rather than growing once per `conv_id` seen for the daemon's life.
//! Test: `chat_tests.rs::concurrent_turns_same_conv_id_persist_both_rounds`
//! (two concurrent same-id turns both persist — the pre-fix loss is gone) and
//! `different_conv_ids_do_not_serialize` (distinct ids run without blocking).
use HashMap;
use ;
use ;
/// A registry of per-`conv_id` async turn locks (#1309).
///
/// Why: the SM agent is shared (an `Arc<SessionManagerAgent>` on the daemon
/// state), so a single registry living on the agent lets every concurrent chat
/// turn coordinate through the SAME per-`conv_id` lock. Cloning the agent shares
/// the registry (it is an `Arc` field), so serialization holds across clones too.
/// What: wraps a [`std::sync::Mutex`] over a map from `conv_id` to a [`Weak`]
/// handle on that conversation's [`AsyncMutex`]. The std mutex guards ONLY the
/// tiny map lookup/insert (never held across an await); the async mutex is the one
/// a turn actually holds across its `open → record → save` critical section.
/// Test: see the module-level tests.
pub