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
//! WP-5 seam — the dynamic-JSON payload surface the Python bindings bind to.
//!
//! A Python event is a `dict` ⇒ a JSON object, so the natural payload is
//! `serde_json::Value`. But there is a sharp edge worth understanding:
//! **`serde_json::Value` cannot be deserialized by the engine's `bincode`
//! payload codec.** `Value` decodes via serde's `deserialize_any` (it works
//! out its shape from the input), and bincode is *not* a self-describing
//! format — it has no shape markers in the bytes, so it returns an error
//! rather than guess. `Salamander<serde_json::Value>` would therefore
//! compile but fail at replay.
//!
//! [`Json`] sidesteps this with no engine change: it wraps a `Value` but
//! serializes *through JSON text* — as a length-prefixed string, which
//! bincode round-trips fine. So [`JsonDb`] = `Salamander<Json>` gives Python
//! dynamic JSON payloads over the whole existing stack (log, projections,
//! time-travel, query layer, group commit). The payload generalization from
//! WP-1 is what makes this a ~40-line newtype rather than a rewrite.
//!
//! (A future *self-describing* payload-format version could store `Value`
//! natively and drop the text round-trip — WP-2's payload-format versioning
//! left that door open. `Json` is the ships-today answer; see
//! `docs/phase-1.5.md` §4.1.)
//!
//! Note `fork` / `session_view` are agent-vocabulary operations on
//! `Salamander<agent::EventBody>` and are *not* available on a `JsonDb`;
//! dynamic-JSON users get the generic engine surface and layer any agent
//! semantics on top in Python.
//!
//! ```
//! use salamander::{Json, JsonDb};
//! use serde_json::json;
//!
//! # fn main() -> salamander::Result<()> {
//! let dir = tempfile::tempdir().unwrap();
//!
//! let mut db: JsonDb = JsonDb::open(dir.path())?;
//! db.append("session-1", Json(json!({ "kind": "user_msg", "text": "hi" })))?;
//! db.append("session-1", Json(json!({ "kind": "tool_call", "tool": "search" })))?;
//! db.commit()?;
//! drop(db); // release the single-writer lock before reopening
//!
//! // Reopen from disk and read the JSON payloads straight back out.
//! let db: JsonDb = JsonDb::open(dir.path())?;
//! let mut kinds = Vec::new();
//! db.replay("session-1", 0..db.head(), |e| {
//! kinds.push(e.body.get("kind").unwrap().as_str().unwrap().to_string());
//! })?;
//! assert_eq!(kinds, ["user_msg", "tool_call"]);
//! # Ok(())
//! # }
//! ```
use Deref;
use ;
use Value;
use crateSalamander;
/// A dynamic-JSON payload: a `serde_json::Value` that serializes through
/// JSON text so it round-trips under the engine's bincode codec (see the
/// [module docs](self) for why the bare `Value` does not). `Deref`s to the
/// inner `Value`, so `payload.get("field")`, `.as_str()`, etc. work directly.
;
/// A `Salamander` whose payload is dynamic JSON (via [`Json`]) — the
/// FFI-facing surface for the Phase-1.5 Python bindings (WP-5). A Python
/// `dict` maps to a `Json`, so events cross the boundary without schema
/// codegen. See the [module docs](self).
pub type JsonDb = ;