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
//! Explicit, backend-independent volume and checkout semantics.
use crate::foundation::GenerationId;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Filesystem behavior profile requested by a volume.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilesystemProfile {
/// Semantics shared by native and browser implementations.
Portable,
/// POSIX-oriented names, links, ownership, and mode behavior.
Posix,
/// Windows-oriented names, reparse points, streams, and security metadata.
Windows,
/// Browser-safe semantics with no host mount assumptions.
Browser,
}
/// Whether a mounted checkout admits mutations.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessMode {
/// Reads only.
ReadOnly,
/// Reads and admitted mutations.
ReadWrite,
}
/// How a checkout observes newer published generations.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConsistencyMode {
/// The selected generation never advances implicitly.
Pinned,
/// Advance only when observation and mutation proofs establish safety.
TrackingSafe,
/// Receive generation changes and apply them at safe boundaries.
Live,
/// Report newer heads and require an explicit caller refresh.
Manual,
}
/// How concurrent writers are admitted.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConcurrencyMode {
/// Every writable head checkout atomically advances the authority epoch;
/// opening a replacement writer permanently fences all prior writers.
ExclusiveWriter,
/// Writers publish against an expected immutable generation and receive a
/// typed conflict instead of implicit retry or merge.
Optimistic,
/// One authority orders every admitted direct-live mutation; safe disjoint
/// races may rebase and retry within the caller's explicit bounds.
SerializedAuthority,
}
/// Persistence expectation of a volume.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Lifecycle {
/// State may disappear when its owning runtime ends.
Ephemeral,
/// Acknowledged state must survive restart under the backend contract.
Durable,
}
/// Where checkout mutations accumulate before publication.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MutationMode {
/// Mutations accumulate in a private sparse copy-on-write overlay.
PrivateOverlay,
/// Mutations are submitted directly to a serialized live authority.
DirectLive,
/// The checkout cannot mutate.
None,
}
/// Case comparison policy for logical names.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaseSensitivity {
/// Compare canonical name bytes exactly.
Sensitive,
/// Use the selected platform profile's case-folding rules.
ProfileFolded,
}
/// Unicode normalization policy applied at the parsing boundary.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnicodePolicy {
/// Preserve admitted scalar sequences exactly.
Preserve,
/// Require canonical NFC input and reject non-normalized names.
RequireNfc,
}
/// Hard limits enforced before allocation, traversal, or mutation.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct VolumeLimits {
/// Maximum encoded path bytes.
pub maximum_path_bytes: u32,
/// Maximum encoded bytes in one path component.
pub maximum_component_bytes: u32,
/// Maximum path components.
pub maximum_path_depth: u16,
/// Maximum canonical bytes in one immutable object.
pub maximum_object_bytes: u64,
/// Maximum mutations admitted in one atomic batch.
pub maximum_mutations_per_batch: u32,
/// Maximum exact paths admitted in one shared lookup/stat batch.
pub maximum_paths_per_batch: u32,
/// Maximum distinct observation and mutation regions retained by one checkout.
pub maximum_checkout_dependencies: u32,
/// Maximum directory entries returned in one page.
pub maximum_directory_page_entries: u32,
/// Maximum authenticated B+tree levels followed by one operation.
pub maximum_page_height: u16,
/// Maximum bytes returned by one range request.
pub maximum_read_bytes: u64,
/// Maximum path-independent files authenticated in one generation.
pub maximum_files_per_generation: u64,
/// Maximum distinct immutable objects authenticated in one generation.
pub maximum_objects_per_generation: u64,
/// Maximum cumulative canonical object bytes in one generation closure.
pub maximum_generation_bytes: u64,
}
impl Default for VolumeLimits {
fn default() -> Self {
Self {
maximum_path_bytes: 32 * 1024,
maximum_component_bytes: 255,
maximum_path_depth: 1_024,
maximum_object_bytes: 64 * 1024 * 1024,
maximum_mutations_per_batch: 2_048,
maximum_paths_per_batch: 65_536,
maximum_checkout_dependencies: 262_144,
maximum_directory_page_entries: 1_024,
maximum_page_height: 64,
maximum_read_bytes: 16 * 1024 * 1024,
maximum_files_per_generation: 16 * 1024 * 1024,
maximum_objects_per_generation: 64 * 1024 * 1024,
maximum_generation_bytes: 1024 * 1024 * 1024 * 1024,
}
}
}
/// Complete immutable volume configuration.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct VolumeConfig {
/// Requested filesystem semantics.
pub profile: FilesystemProfile,
/// Writer coordination contract.
pub concurrency: ConcurrencyMode,
/// Persistence expectation.
pub lifecycle: Lifecycle,
/// Logical name comparison policy.
pub case_sensitivity: CaseSensitivity,
/// Unicode admission policy.
pub unicode: UnicodePolicy,
/// Whether symbolic links are representable.
pub symbolic_links: bool,
/// Whether multiple names may share one stable file record.
pub hard_links: bool,
/// Whether holes and allocated-zero extents remain distinct.
pub sparse_files: bool,
/// Mandatory resource and work bounds.
pub limits: VolumeLimits,
}
impl VolumeConfig {
/// Constructs the fully portable profile with explicit persistence.
#[must_use]
pub fn portable(lifecycle: Lifecycle) -> Self {
Self {
profile: FilesystemProfile::Portable,
concurrency: ConcurrencyMode::Optimistic,
lifecycle,
case_sensitivity: CaseSensitivity::Sensitive,
unicode: UnicodePolicy::Preserve,
symbolic_links: true,
hard_links: true,
sparse_files: true,
limits: VolumeLimits::default(),
}
}
/// Constructs the host-native filesystem profile for a mounted checkout.
#[must_use]
#[cfg(not(target_arch = "wasm32"))]
pub fn native(lifecycle: Lifecycle) -> Self {
let mut limits = VolumeLimits::default();
if cfg!(windows) {
// Windows names are encoded as UTF-16LE in the SDK; NTFS admits
// 255 code units per component, not 255 encoded bytes.
limits.maximum_component_bytes = 255 * 2;
}
Self {
profile: if cfg!(windows) {
FilesystemProfile::Windows
} else {
FilesystemProfile::Posix
},
limits,
..Self::portable(lifecycle)
}
}
/// Validates all limits and cross-field semantic requirements.
///
/// # Errors
///
/// Returns a stable configuration error before any backend is opened.
pub fn validate(self) -> Result<Self, VolumeConfigError> {
let limits = self.limits;
if limits.maximum_path_bytes == 0
|| limits.maximum_component_bytes == 0
|| limits.maximum_path_depth == 0
|| limits.maximum_object_bytes == 0
|| limits.maximum_mutations_per_batch == 0
|| limits.maximum_paths_per_batch == 0
|| limits.maximum_checkout_dependencies == 0
|| limits.maximum_directory_page_entries == 0
|| limits.maximum_page_height == 0
|| limits.maximum_read_bytes == 0
|| limits.maximum_files_per_generation == 0
|| limits.maximum_objects_per_generation == 0
|| limits.maximum_generation_bytes == 0
{
return Err(VolumeConfigError::ZeroLimit);
}
if limits.maximum_component_bytes > limits.maximum_path_bytes {
return Err(VolumeConfigError::ComponentExceedsPath);
}
if limits.maximum_directory_page_entries < 2 {
return Err(VolumeConfigError::InsufficientPageFanout);
}
Ok(self)
}
}
/// Generation selected when a checkout opens.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GenerationSelector {
/// Resolve the volume head once at checkout admission.
Head,
/// Open one exact immutable generation.
Exact(GenerationId),
}
/// Complete checkout behavior chosen by a consumer.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CheckoutMode {
/// Whether mutations are admitted.
pub access: AccessMode,
/// How newer generations are observed.
pub consistency: ConsistencyMode,
/// Where admitted mutations accumulate.
pub mutations: MutationMode,
}
impl CheckoutMode {
/// Immutable exact-generation reader.
#[must_use]
pub const fn read_only_pinned() -> Self {
Self {
access: AccessMode::ReadOnly,
consistency: ConsistencyMode::Pinned,
mutations: MutationMode::None,
}
}
/// Optimistic writable sparse overlay with observation-safe tracking.
#[must_use]
pub const fn tracking_transaction() -> Self {
Self {
access: AccessMode::ReadWrite,
consistency: ConsistencyMode::TrackingSafe,
mutations: MutationMode::PrivateOverlay,
}
}
/// Validates cross-field checkout behavior.
///
/// # Errors
///
/// Rejects writable/read-only contradictions and direct-live use without
/// live consistency.
pub const fn validate(self) -> Result<Self, CheckoutModeError> {
match (self.access, self.mutations, self.consistency) {
(AccessMode::ReadOnly, MutationMode::None, _)
| (AccessMode::ReadWrite, MutationMode::DirectLive, ConsistencyMode::Live)
| (AccessMode::ReadWrite, MutationMode::PrivateOverlay, _) => Ok(self),
(AccessMode::ReadOnly, _, _) => Err(CheckoutModeError::ReadOnlyMutation),
(AccessMode::ReadWrite, MutationMode::None, _) => {
Err(CheckoutModeError::WritableWithoutMutationMode)
}
(AccessMode::ReadWrite, MutationMode::DirectLive, _) => {
Err(CheckoutModeError::DirectRequiresLive)
}
}
}
}
/// Volume configuration failures.
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum VolumeConfigError {
/// Every work or allocation limit must be positive.
#[error("volume limits must be non-zero")]
ZeroLimit,
/// One component cannot be larger than the complete admitted path.
#[error("maximum component bytes exceeds maximum path bytes")]
ComponentExceedsPath,
/// Persistent trees require at least two entries per internal page to make progress.
#[error("maximum directory page entries must be at least two")]
InsufficientPageFanout,
}
/// Checkout configuration failures.
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum CheckoutModeError {
/// A read-only checkout selected a mutation mode.
#[error("read-only checkout cannot admit mutations")]
ReadOnlyMutation,
/// A writable checkout omitted its mutation mode.
#[error("writable checkout requires a mutation mode")]
WritableWithoutMutationMode,
/// Direct mutation requires a live serialized view.
#[error("direct mutation requires live consistency")]
DirectRequiresLive,
}
#[cfg(test)]
#[path = "tests/model.rs"]
mod tests;