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
// SPDX-FileCopyrightText: 2026 INTERCHAINED LLC
// SPDX-License-Identifier: BUSL-1.1
// NEDB · © 2026 INTERCHAINED LLC × Eth-Interchained × Vex (Claude Opus 5)
//! Collection identity — what it means for a collection to EXIST.
//!
//! # Why this module exists
//!
//! Before this, "which collections exist" was not a fact about the database.
//! It was a fact about the storage substrate, and the two substrates disagreed:
//!
//! ```text
//! disk, flush between PUT and DELETE : ["orders"]
//! disk, both inside one flush tick : []
//! memory : []
//! ```
//!
//! All three are the same logical history — create a collection, then empty it.
//! Disk mode answered by listing directories ([`crate::index::IdIndex::collections`]
//! did a `read_dir`), and the WAL write buffer is keyed by `(coll, id)`, so a PUT
//! followed by a DELETE before the 1-second flush ticker fires overwrites the
//! buffered entry with its own tombstone. No directory is ever created. PUT,
//! flush, DELETE leaves the directory behind forever, because the flush path only
//! ever calls `remove_file` — it has no `remove_dir` in it at all.
//!
//! So the namespace was decided by a background timer. That is survivable for a
//! `LIST COLLECTIONS` convenience call, and fatal for a state root: a root
//! commits to a namespace, which is only meaningful if two replicas of the same
//! history agree on what the namespace IS.
//!
//! # The rule
//!
//! A collection exists because a record says so, not because a directory is
//! lying around. Creation is an event, the event is a node, and the node lives
//! in the DAG like everything else. Emptying a collection does not destroy it;
//! only an explicit drop does, and a drop is a tombstone rather than an absence.
//!
//! Putting the registry in the DAG rather than in a sidecar file is the boring
//! choice and it pays three times: `since()` replicates collection creation to
//! followers for free, `AS OF` answers "which collections existed at seq N"
//! for free, and `verify()` covers the registry for free. A `COLLECTIONS` file
//! would have needed all three written by hand.
//!
//! # Reserved names
//!
//! The registry has to live somewhere, and wherever it lives must not be
//! user-writable — otherwise a client can forge the namespace by writing to it
//! directly. The same reservation is what will later keep state-root records
//! from being part of the state they describe, which is a decent sign it is the
//! right primitive: one rule, used twice.
use ;
/// Everything under this prefix belongs to the engine. User writes are refused.
pub const RESERVED_PREFIX: &str = "_nedb";
/// The collection registry. Ids are collection names; the latest version of
/// each says whether that collection is currently live.
pub const COLLECTIONS: &str = "_nedb.collections";
/// Persisted state roots. Ids are zero-padded sequence numbers so that the
/// index's lexicographic id ordering is also numeric ordering.
///
/// Reserved for the reason the reservation exists at all: a root record that
/// counted as part of the state would change the state it describes, so
/// computing one would immediately invalidate it.
pub const ROOTS: &str = "_nedb.roots";
/// Engine metadata that is neither a collection record nor a root: the history
/// floor lives here. Kept out of `ROOTS` so that collection stays homogeneous
/// and `list_roots` never has to skip an entry it cannot parse -- an entry
/// skipped silently is indistinguishable from one that failed to parse.
pub const META: &str = "_nedb.meta";
/// Zero-padded so lexicographic ordering is numeric ordering. `u64::MAX` is 20
/// digits.
/// Is this name part of the engine's own namespace?
/// Refuse a write the caller is not allowed to make.
///
/// Named for what it does to the caller, not for what it returns, because the
/// only correct response at every call site is to stop.
/// Is this a name a collection can durably HAVE?
///
/// Two separate concerns land here.
///
/// The first is that a collection name becomes a directory name on disk
/// (`indexes/{coll}/{shard}/{id}`), so a name containing a path separator or a
/// `..` component does not address a collection at all — it addresses somewhere
/// else on the filesystem. That has to be refused at the entry point rather than
/// sanitised, because a silently rewritten name is a different collection than
/// the one the caller asked for, and they would never be told.
///
/// The second is that a state root commits to these names. A name that cannot
/// round-trip identically through every storage path is not an identity.
/// A name that may be written to: valid AND not engine-owned.