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
//! Palace → JSONL export (#5902).
//!
//! Why: the palace had no export of any kind, so there was no artefact two
//! machines could exchange. JSONL is the format `trusty-agents memories export`
//! already uses and the one a git-committed file wants: one memory per line, so a
//! diff shows added and removed facts rather than a reflowed blob, and a
//! half-written file loses only its last line.
//! What: [`export_palace_records`] (the pure half: drawers to records) and
//! [`export_palace_jsonl`] (write them to a path).
//! Test: `export_writes_one_line_per_drawer`, `export_then_import_preserves_metadata`,
//! `export_is_ordered_by_hash` in `share::tests`.
use HashMap;
use Write as _;
use Path;
use ;
use Uuid;
use SharedMemoryRecord;
use cratePalaceHandle;
use cratelist_room_summaries;
/// Every shareable memory in `handle`, as records, ordered by content hash.
///
/// Why the ordering is by hash and not by time: the file is committed to git, and
/// a stable order is what keeps a re-export from producing a diff that has
/// nothing to do with what changed. Content hash is the only key both machines
/// compute identically — `created_at` differs between two machines' copies of one
/// fact by construction, and drawer UUIDs are v4.
///
/// Why every drawer and not a filtered set: filtering is the caller's business,
/// and the caller here is a CLI command that does not exist yet. Two exclusions
/// are structural rather than policy, so they live here: a drawer whose TTL has
/// already elapsed is not a fact any more (`Drawer::is_expired_at`), and a Tier C
/// drawer holds a point-in-time claim whose retirement condition is local to the
/// machine that wrote it (`Drawer::is_tier_c`) — shipping `pr:4818/state` to
/// another machine would assert a stale slot there.
///
/// 🔴 SECURITY: nothing on this path screens content for secrets.
/// `memory_core::filter::check_secret` runs at WRITE time only, so any credential
/// that predates the secret filter, or that the filter's heuristics missed, is
/// already sitting in a palace and this function will copy it out verbatim. That
/// is acceptable while the destination is a local file. It is NOT acceptable for
/// the git-commit workflow this primitive exists to serve, which must gate on a
/// re-scan of every exported record — see #5902 and #1683.
///
/// What: reads the in-memory drawer table (a complete mirror of `DRAWERS`), maps
/// each drawer's `room_id` to its registered label, and returns one record per
/// surviving drawer. A drawer whose room is not in the registry falls back to
/// `"general"`, matching how recall treats an unregistered room.
/// Test: `export_writes_one_line_per_drawer`, `export_skips_expired_and_tier_c`,
/// `export_is_ordered_by_hash`.
/// Write `handle`'s shareable memories to `path` as JSONL. Returns the line
/// count.
///
/// Why the write is atomic: the file is the input to an import on another
/// machine, and a torn file is one an importer would read as a truncated
/// export — silently short rather than obviously broken. Writing to a sibling
/// temp file and renaming is the same discipline `PalaceStore::save_palace` and
/// `L1Cache::save_l1_cache` already use.
/// What: creates `path`'s parent, serializes one record per line to
/// `<path>.tmp`, then renames over `path`.
/// Test: `export_writes_one_line_per_drawer`, `export_then_import_preserves_metadata`.
/// Serialize `records` to `path` as one JSON object per line, atomically.
///
/// Why separate from [`export_palace_jsonl`]: PR 2's commit flow needs to write a
/// filtered or merged record set it assembled itself, and tests need to write a
/// hand-built file without standing up a palace.
/// What: as described on [`export_palace_jsonl`].
/// Test: `export_then_import_preserves_metadata`, `two_machines_converge_on_one_memory`.
/// `room_id` → registered label for every room in this palace.
///
/// Why: the export carries the room LABEL so a receiving palace can mint the
/// same UUIDv5 id from it (ADR-0027), and the label lives in the ROOMS registry
/// rather than on the drawer.
/// What: one registry read, collected into a map so the per-drawer lookup is not
/// a redb round trip.
/// Test: `export_then_import_preserves_metadata` asserts the label survives.