Skip to main content

acyclic_fs/
lazy_workspace.rs

1//! Source-backed sparse workspaces.
2//!
3//! A lazy workspace keeps unresolved host state outside the authenticated
4//! authored checkout. Forking copies only one immutable overlay identifier;
5//! paths are observed individually through [`DemandSource`].
6
7use crate::demand::{
8    DemandError, DemandSource, SourceCursor, SourceDirectoryEntry, SourceNode, SourceNodeKind,
9    SourceReference, SourceVersion,
10};
11use crate::kernel::{
12    FileKind, FileMetadata, FilePayload, FileRecord, LogicalName, MetadataField, NameEncoding,
13    NamespacePath,
14};
15use crate::model::VolumeConfig;
16use crate::path::PortablePath;
17use crate::{
18    AsyncAuthorityStore, AsyncObjectStore, AuthoredMutation, CancellationToken, FileId,
19    ForkOptions, Fs, IdempotencyKey, OperationReceipt, TransactionCommit, WorkBudget, WorkCounters,
20    Workspace, WorkspaceDirectoryEntry, WorkspaceError, WorkspaceExtentKind, WorkspaceId,
21    WorkspaceStat,
22};
23use async_trait::async_trait;
24use bytes::Bytes;
25use serde::{Deserialize, Serialize};
26use std::collections::BTreeMap;
27use std::convert::Infallible;
28use std::future::Future;
29use std::pin::Pin;
30use std::sync::{Arc, Mutex};
31use thiserror::Error;
32
33#[cfg(target_arch = "wasm32")]
34type LazyFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
35#[cfg(not(target_arch = "wasm32"))]
36type LazyFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
37
38const LAZY_STATE_SCHEMA: u32 = 7;
39const MAXIMUM_STATE_RETRIES: usize = 32;
40
41const LAZY_SNAPSHOT_DOMAIN: &[u8] = b"acyclic-fs-lazy-snapshot-v1\0";
42
43/// Stable logical identity of one constant-size lazy tree snapshot.
44#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
45#[serde(transparent)]
46pub struct LazySnapshotId([u8; 32]);
47
48impl LazySnapshotId {
49    /// Canonical digest bytes.
50    #[must_use]
51    pub const fn into_bytes(self) -> [u8; 32] {
52        self.0
53    }
54}
55
56/// Constant-size reference to an authored generation plus unresolved source state.
57#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct LazySnapshotRef {
59    /// Stable logical identity over every represented component.
60    pub id: LazySnapshotId,
61    /// Workspace authenticating authored copy-on-write state.
62    pub workspace_id: WorkspaceId,
63    /// Immutable authored generation.
64    pub authored_generation: crate::GenerationId,
65    /// Live source identity and invalidation epoch.
66    pub source: SourceReference,
67    /// Immutable first-observation/tombstone overlay root.
68    pub overlay: LazyOverlayId,
69    /// Immutable source-identity shadow root.
70    pub shadows: LazyShadowId,
71}
72
73impl LazySnapshotRef {
74    fn new(
75        workspace_id: WorkspaceId,
76        authored_generation: crate::GenerationId,
77        source: SourceReference,
78        overlay: LazyOverlayId,
79        shadows: LazyShadowId,
80    ) -> Self {
81        let mut hasher = blake3::Hasher::new();
82        hasher.update(LAZY_SNAPSHOT_DOMAIN);
83        hasher.update(&workspace_id.into_bytes());
84        hasher.update(authored_generation.digest().as_bytes());
85        hasher.update(&source.identity);
86        hasher.update(&source.epoch.to_le_bytes());
87        hasher.update(&overlay.into_bytes());
88        hasher.update(&shadows.into_bytes());
89        Self {
90            id: LazySnapshotId(*hasher.finalize().as_bytes()),
91            workspace_id,
92            authored_generation,
93            source,
94            overlay,
95            shadows,
96        }
97    }
98}
99
100/// Content address of one immutable observed/tombstone overlay.
101#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
102#[serde(transparent)]
103pub struct LazyOverlayId([u8; 32]);
104
105impl LazyOverlayId {
106    /// Reconstructs a persisted overlay identifier.
107    #[must_use]
108    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
109        Self(bytes)
110    }
111
112    /// Stable raw identifier.
113    #[must_use]
114    pub const fn into_bytes(self) -> [u8; 32] {
115        self.0
116    }
117}
118
119/// Durable constant-size binding for one sparse workspace.
120#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
121#[serde(try_from = "LazyWorkspaceStateRecord")]
122pub struct LazyWorkspaceState {
123    /// Serialization schema.
124    pub schema_version: u32,
125    /// Monotonic CAS revision.
126    pub revision: u64,
127    /// Workspace whose authored generation supplies the writable overlay.
128    pub workspace_id: WorkspaceId,
129    /// Direct sparse parent, when this state was produced by a fork.
130    pub parent_workspace_id: Option<WorkspaceId>,
131    /// Provider identity and invalidation epoch pinned at attach time.
132    pub source: SourceReference,
133    /// Immutable observations and tombstones visible to this context.
134    pub overlay: LazyOverlayId,
135    /// Persistent source identity to latest authored record index.
136    pub shadows: LazyShadowId,
137    /// Prepared authored removal recovered atomically when the workspace reopens.
138    pub pending_remove: Option<PendingLazyRemove>,
139}
140
141/// Content address of one immutable source-identity shadow index node.
142#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
143#[serde(transparent)]
144pub struct LazyShadowId([u8; 32]);
145
146impl LazyShadowId {
147    /// Stable raw identifier.
148    #[must_use]
149    pub const fn into_bytes(self) -> [u8; 32] {
150        self.0
151    }
152}
153
154/// Persistent treap mapping a source-derived identity to its latest authored record.
155#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
156pub enum LazyShadow {
157    /// Shared empty index root.
158    #[default]
159    Empty,
160    /// One identity record and immutable child indexes.
161    Node {
162        /// Source-derived stable identity used as the search key.
163        file_id: FileId,
164        /// Deterministic treap priority.
165        priority: u64,
166        /// Canonically encoded immutable file record.
167        record: Vec<u8>,
168        /// Projected scalar metadata authenticated with the record.
169        metadata: Box<crate::WorkspaceMetadata>,
170        /// Identities ordered before this one.
171        left: LazyShadowId,
172        /// Identities ordered after this one.
173        right: LazyShadowId,
174    },
175}
176
177impl LazyShadow {
178    fn id(&self) -> Result<LazyShadowId, LazyWorkspaceError> {
179        let encoded = serde_json::to_vec(self)
180            .map_err(|error| LazyWorkspaceError::Store(error.to_string()))?;
181        Ok(LazyShadowId(*blake3::hash(&encoded).as_bytes()))
182    }
183}
184
185/// Durable intent that makes authored removal and source tombstoning recoverable.
186#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct PendingLazyRemove {
188    /// Overlay visible before the removal began.
189    pub prior_overlay: LazyOverlayId,
190    /// Identity records visible before the removal began.
191    pub prior_shadows: LazyShadowId,
192    /// Overlay prepared for the same durable publication decision.
193    pub prepared_overlay: LazyOverlayId,
194    /// Whether recovery must consult a durable authored operation.
195    pub kind: PendingLazyRemoveKind,
196}
197
198/// Durable decision needed to recover a prepared lazy removal.
199#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub enum PendingLazyRemoveKind {
201    /// The removed binding existed only in the lazy source.
202    SourceOnly,
203    /// An authored removal is identified by this stable publication key.
204    Authored {
205        /// Idempotency key persisted before the authored transaction begins.
206        idempotency_key: IdempotencyKey,
207    },
208    /// Mounted removals staged with the checkout and published at its boundary.
209    Mounted {
210        /// The checkout publication whose result decides this overlay.
211        idempotency_key: IdempotencyKey,
212    },
213}
214
215/// Schema 5 (released in 0.1.0) differs only in its pending-removal layout.
216const LAZY_STATE_SCHEMA_V5: u32 = 5;
217
218#[derive(Deserialize)]
219struct LazyWorkspaceStateRecord {
220    schema_version: u32,
221    revision: u64,
222    workspace_id: WorkspaceId,
223    parent_workspace_id: Option<WorkspaceId>,
224    source: SourceReference,
225    overlay: LazyOverlayId,
226    shadows: LazyShadowId,
227    pending_remove: Option<PendingLazyRemoveRecord>,
228}
229
230#[derive(Deserialize)]
231#[serde(untagged)]
232enum PendingLazyRemoveRecord {
233    Current(PendingLazyRemove),
234    V5 {
235        prior_overlay: LazyOverlayId,
236        tombstone_overlay: LazyOverlayId,
237        kind: PendingLazyRemoveKind,
238    },
239}
240
241impl TryFrom<LazyWorkspaceStateRecord> for LazyWorkspaceState {
242    type Error = &'static str;
243
244    fn try_from(record: LazyWorkspaceStateRecord) -> Result<Self, Self::Error> {
245        let (schema_version, pending_remove) = match (record.schema_version, record.pending_remove)
246        {
247            (LAZY_STATE_SCHEMA_V5, None) => (LAZY_STATE_SCHEMA, None),
248            // Schema 5 prepared shadows in place and never rolled them back, so
249            // the current shadows are exactly what its recovery would keep.
250            (
251                LAZY_STATE_SCHEMA_V5,
252                Some(PendingLazyRemoveRecord::V5 {
253                    prior_overlay,
254                    tombstone_overlay,
255                    kind,
256                }),
257            ) => (
258                LAZY_STATE_SCHEMA,
259                Some(PendingLazyRemove {
260                    prior_overlay,
261                    prior_shadows: record.shadows,
262                    prepared_overlay: tombstone_overlay,
263                    kind,
264                }),
265            ),
266            (LAZY_STATE_SCHEMA_V5, Some(PendingLazyRemoveRecord::Current(_))) => {
267                return Err("schema 5 lazy state has a current pending removal");
268            }
269            (schema, None) => (schema, None),
270            (schema, Some(PendingLazyRemoveRecord::Current(pending))) => (schema, Some(pending)),
271            (_, Some(PendingLazyRemoveRecord::V5 { .. })) => {
272                return Err("lazy state has a schema 5 pending removal");
273            }
274        };
275        Ok(Self {
276            schema_version,
277            revision: record.revision,
278            workspace_id: record.workspace_id,
279            parent_workspace_id: record.parent_workspace_id,
280            source: record.source,
281            overlay: record.overlay,
282            shadows: record.shadows,
283            pending_remove,
284        })
285    }
286}
287
288/// One immutable node in the persistent source-knowledge index.
289///
290/// The deterministic treap keeps attach and fork O(1), mutations O(log n),
291/// and exact lookups O(log n) without rewriting the complete observed set.
292#[allow(clippy::large_enum_variant)]
293#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
294pub enum LazyOverlay {
295    /// Shared empty index root.
296    #[default]
297    Empty,
298    /// One path fact and its immutable child indexes.
299    Node {
300        /// Portable absolute path used as the search key.
301        path: String,
302        /// Deterministic heap priority derived from `path`.
303        priority: u64,
304        /// Source observation or authored source tombstone.
305        change: LazyOverlayChange,
306        /// Keys ordered before this path.
307        left: LazyOverlayId,
308        /// Keys ordered after this path.
309        right: LazyOverlayId,
310    },
311}
312
313/// One fact introduced by a lazy overlay delta.
314#[allow(clippy::large_enum_variant)]
315#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
316pub enum LazyOverlayChange {
317    /// First exact observation of one demanded path.
318    Observe {
319        /// Source generation against which this fact was authenticated.
320        source: SourceReference,
321        /// Immutable source fact.
322        node: SourceNode,
323    },
324    /// Authored removal of a source path or subtree.
325    Tombstone,
326}
327
328impl LazyOverlay {
329    fn id(&self) -> Result<LazyOverlayId, LazyWorkspaceError> {
330        let encoded = serde_json::to_vec(self)
331            .map_err(|error| LazyWorkspaceError::Store(error.to_string()))?;
332        Ok(LazyOverlayId(*blake3::hash(&encoded).as_bytes()))
333    }
334}
335
336/// Durable companion storage for constant-size lazy bindings and immutable overlays.
337#[async_trait]
338pub trait LazyWorkspaceStore: Clone + Send + Sync + 'static {
339    /// Backend error.
340    type Error: std::error::Error + Send + Sync + 'static;
341
342    /// Loads one workspace binding.
343    async fn load_lazy_workspace(
344        &self,
345        workspace_id: WorkspaceId,
346    ) -> Result<Option<LazyWorkspaceState>, Self::Error>;
347
348    /// Loads one binding under an explicit logical durable-read budget.
349    async fn load_lazy_workspace_measured(
350        &self,
351        workspace_id: WorkspaceId,
352        budget: WorkBudget,
353        cancellation: &CancellationToken,
354    ) -> Result<OperationReceipt<Option<LazyWorkspaceState>>, LazyWorkspaceError> {
355        measured_lazy_store_read(budget, cancellation)?;
356        let value = self
357            .load_lazy_workspace(workspace_id)
358            .await
359            .map_err(store_error)?;
360        cancellation
361            .check()
362            .map_err(|_| LazyWorkspaceError::Cancelled)?;
363        Ok(OperationReceipt {
364            value,
365            work: lazy_store_read_work(),
366        })
367    }
368
369    /// Replaces one binding only at the expected revision (`0` means absent).
370    async fn compare_and_swap_lazy_workspace(
371        &self,
372        workspace_id: WorkspaceId,
373        expected_revision: u64,
374        replacement: LazyWorkspaceState,
375    ) -> Result<bool, Self::Error>;
376
377    /// Replaces one binding under an explicit logical durable-write budget.
378    async fn compare_and_swap_lazy_workspace_measured(
379        &self,
380        workspace_id: WorkspaceId,
381        expected_revision: u64,
382        replacement: LazyWorkspaceState,
383        budget: WorkBudget,
384        cancellation: &CancellationToken,
385    ) -> Result<OperationReceipt<bool>, LazyWorkspaceError> {
386        measured_lazy_store_write(budget, cancellation)?;
387        let value = self
388            .compare_and_swap_lazy_workspace(workspace_id, expected_revision, replacement)
389            .await
390            .map_err(store_error)?;
391        cancellation
392            .check()
393            .map_err(|_| LazyWorkspaceError::Cancelled)?;
394        Ok(OperationReceipt {
395            value,
396            work: lazy_store_write_work(),
397        })
398    }
399
400    /// Loads an immutable content-addressed overlay.
401    async fn load_lazy_overlay(
402        &self,
403        overlay: LazyOverlayId,
404    ) -> Result<Option<LazyOverlay>, Self::Error>;
405
406    /// Loads one immutable overlay under an explicit logical read budget.
407    async fn load_lazy_overlay_measured(
408        &self,
409        overlay: LazyOverlayId,
410        budget: WorkBudget,
411        cancellation: &CancellationToken,
412    ) -> Result<OperationReceipt<Option<LazyOverlay>>, LazyWorkspaceError> {
413        measured_lazy_store_read(budget, cancellation)?;
414        let value = self.load_lazy_overlay(overlay).await.map_err(store_error)?;
415        cancellation
416            .check()
417            .map_err(|_| LazyWorkspaceError::Cancelled)?;
418        Ok(OperationReceipt {
419            value,
420            work: lazy_store_read_work(),
421        })
422    }
423
424    /// Stores an immutable overlay, idempotently rejecting digest mismatches.
425    async fn put_lazy_overlay(
426        &self,
427        overlay: LazyOverlayId,
428        value: LazyOverlay,
429    ) -> Result<(), Self::Error>;
430
431    /// Loads one immutable source-identity shadow node.
432    async fn load_lazy_shadow(
433        &self,
434        shadow: LazyShadowId,
435    ) -> Result<Option<LazyShadow>, Self::Error>;
436
437    /// Loads one immutable shadow under an explicit logical read budget.
438    async fn load_lazy_shadow_measured(
439        &self,
440        shadow: LazyShadowId,
441        budget: WorkBudget,
442        cancellation: &CancellationToken,
443    ) -> Result<OperationReceipt<Option<LazyShadow>>, LazyWorkspaceError> {
444        measured_lazy_store_read(budget, cancellation)?;
445        let value = self.load_lazy_shadow(shadow).await.map_err(store_error)?;
446        cancellation
447            .check()
448            .map_err(|_| LazyWorkspaceError::Cancelled)?;
449        Ok(OperationReceipt {
450            value,
451            work: lazy_store_read_work(),
452        })
453    }
454
455    /// Stores one immutable source-identity shadow node.
456    async fn put_lazy_shadow(
457        &self,
458        shadow: LazyShadowId,
459        value: LazyShadow,
460    ) -> Result<(), Self::Error>;
461}
462
463/// In-memory companion store for deterministic tests and embedded use.
464#[derive(Clone, Default)]
465pub struct MemoryLazyWorkspaceStore {
466    inner: Arc<Mutex<MemoryLazyWorkspaceState>>,
467}
468
469#[derive(Default)]
470struct MemoryLazyWorkspaceState {
471    workspaces: BTreeMap<WorkspaceId, LazyWorkspaceState>,
472    overlays: BTreeMap<LazyOverlayId, LazyOverlay>,
473    shadows: BTreeMap<LazyShadowId, LazyShadow>,
474}
475
476#[async_trait]
477impl LazyWorkspaceStore for MemoryLazyWorkspaceStore {
478    type Error = Infallible;
479
480    async fn load_lazy_workspace(
481        &self,
482        workspace_id: WorkspaceId,
483    ) -> Result<Option<LazyWorkspaceState>, Self::Error> {
484        let state = self
485            .inner
486            .lock()
487            .unwrap_or_else(|poisoned| poisoned.into_inner());
488        Ok(state.workspaces.get(&workspace_id).cloned())
489    }
490
491    async fn compare_and_swap_lazy_workspace(
492        &self,
493        workspace_id: WorkspaceId,
494        expected_revision: u64,
495        replacement: LazyWorkspaceState,
496    ) -> Result<bool, Self::Error> {
497        let mut state = self
498            .inner
499            .lock()
500            .unwrap_or_else(|poisoned| poisoned.into_inner());
501        let actual = state
502            .workspaces
503            .get(&workspace_id)
504            .map_or(0, |current| current.revision);
505        if actual != expected_revision {
506            return Ok(false);
507        }
508        state.workspaces.insert(workspace_id, replacement);
509        Ok(true)
510    }
511
512    async fn load_lazy_overlay(
513        &self,
514        overlay: LazyOverlayId,
515    ) -> Result<Option<LazyOverlay>, Self::Error> {
516        let state = self
517            .inner
518            .lock()
519            .unwrap_or_else(|poisoned| poisoned.into_inner());
520        Ok(state.overlays.get(&overlay).cloned())
521    }
522
523    async fn put_lazy_overlay(
524        &self,
525        overlay: LazyOverlayId,
526        value: LazyOverlay,
527    ) -> Result<(), Self::Error> {
528        let mut state = self
529            .inner
530            .lock()
531            .unwrap_or_else(|poisoned| poisoned.into_inner());
532        if let Some(existing) = state.overlays.get(&overlay) {
533            assert_eq!(existing, &value, "content-addressed overlay collision");
534        } else {
535            state.overlays.insert(overlay, value);
536        }
537        Ok(())
538    }
539
540    async fn load_lazy_shadow(
541        &self,
542        shadow: LazyShadowId,
543    ) -> Result<Option<LazyShadow>, Self::Error> {
544        let state = self
545            .inner
546            .lock()
547            .unwrap_or_else(|poisoned| poisoned.into_inner());
548        Ok(state.shadows.get(&shadow).cloned())
549    }
550
551    async fn put_lazy_shadow(
552        &self,
553        shadow: LazyShadowId,
554        value: LazyShadow,
555    ) -> Result<(), Self::Error> {
556        let mut state = self
557            .inner
558            .lock()
559            .unwrap_or_else(|poisoned| poisoned.into_inner());
560        if let Some(existing) = state.shadows.get(&shadow) {
561            assert_eq!(existing, &value, "content-addressed shadow collision");
562        } else {
563            state.shadows.insert(shadow, value);
564        }
565        Ok(())
566    }
567}
568
569#[derive(Clone)]
570struct ImmutableTreapNode<K, V, I> {
571    key: K,
572    priority: u64,
573    value: V,
574    left: I,
575    right: I,
576}
577
578#[async_trait]
579trait ImmutableTreap: Sync {
580    type Id: Copy + Send + Sync;
581    type Key: Clone + Ord + Send + Sync;
582    type Value: Clone + Send + Sync;
583
584    async fn load(
585        &self,
586        id: Self::Id,
587    ) -> Result<Option<ImmutableTreapNode<Self::Key, Self::Value, Self::Id>>, LazyWorkspaceError>;
588
589    async fn store(
590        &self,
591        node: ImmutableTreapNode<Self::Key, Self::Value, Self::Id>,
592    ) -> Result<Self::Id, LazyWorkspaceError>;
593
594    fn priority(key: &Self::Key) -> u64;
595}
596
597fn insert_immutable_treap<'a, T: ImmutableTreap>(
598    treap: &'a T,
599    root: T::Id,
600    key: T::Key,
601    value: T::Value,
602) -> LazyFuture<'a, Result<T::Id, LazyWorkspaceError>> {
603    Box::pin(async move {
604        let Some(current) = treap.load(root).await? else {
605            return treap
606                .store(ImmutableTreapNode {
607                    priority: T::priority(&key),
608                    key,
609                    value,
610                    left: root,
611                    right: root,
612                })
613                .await;
614        };
615        match key.cmp(&current.key) {
616            std::cmp::Ordering::Equal => {
617                treap
618                    .store(ImmutableTreapNode {
619                        key,
620                        value,
621                        ..current
622                    })
623                    .await
624            }
625            ordering => {
626                let descend_left = ordering == std::cmp::Ordering::Less;
627                let child_root = if descend_left {
628                    current.left
629                } else {
630                    current.right
631                };
632                let inserted = insert_immutable_treap(treap, child_root, key, value).await?;
633                let child = treap.load(inserted).await?.ok_or_else(|| {
634                    LazyWorkspaceError::Store("inserted lazy index node is absent".to_owned())
635                })?;
636                if child.priority > current.priority {
637                    let rotated = treap
638                        .store(if descend_left {
639                            ImmutableTreapNode {
640                                left: child.right,
641                                ..current
642                            }
643                        } else {
644                            ImmutableTreapNode {
645                                right: child.left,
646                                ..current
647                            }
648                        })
649                        .await?;
650                    return treap
651                        .store(if descend_left {
652                            ImmutableTreapNode {
653                                right: rotated,
654                                ..child
655                            }
656                        } else {
657                            ImmutableTreapNode {
658                                left: rotated,
659                                ..child
660                            }
661                        })
662                        .await;
663                }
664                treap
665                    .store(if descend_left {
666                        ImmutableTreapNode {
667                            left: inserted,
668                            ..current
669                        }
670                    } else {
671                        ImmutableTreapNode {
672                            right: inserted,
673                            ..current
674                        }
675                    })
676                    .await
677            }
678        }
679    })
680}
681
682async fn immutable_treap_value<T: ImmutableTreap>(
683    treap: &T,
684    mut root: T::Id,
685    key: &T::Key,
686) -> Result<Option<T::Value>, LazyWorkspaceError> {
687    loop {
688        let Some(node) = treap.load(root).await? else {
689            return Ok(None);
690        };
691        match key.cmp(&node.key) {
692            std::cmp::Ordering::Equal => return Ok(Some(node.value)),
693            std::cmp::Ordering::Less => root = node.left,
694            std::cmp::Ordering::Greater => root = node.right,
695        }
696    }
697}
698
699struct OverlayTreap<'a, S>(&'a S);
700
701#[async_trait]
702impl<S: LazyWorkspaceStore> ImmutableTreap for OverlayTreap<'_, S> {
703    type Id = LazyOverlayId;
704    type Key = String;
705    type Value = LazyOverlayChange;
706
707    async fn load(
708        &self,
709        id: Self::Id,
710    ) -> Result<Option<ImmutableTreapNode<Self::Key, Self::Value, Self::Id>>, LazyWorkspaceError>
711    {
712        match self.0.load_lazy_overlay(id).await.map_err(store_error)? {
713            Some(LazyOverlay::Empty) => Ok(None),
714            Some(LazyOverlay::Node {
715                path,
716                priority,
717                change,
718                left,
719                right,
720            }) => Ok(Some(ImmutableTreapNode {
721                key: path,
722                priority,
723                value: change,
724                left,
725                right,
726            })),
727            None => Err(LazyWorkspaceError::Store(
728                "lazy overlay is absent".to_owned(),
729            )),
730        }
731    }
732
733    async fn store(
734        &self,
735        node: ImmutableTreapNode<Self::Key, Self::Value, Self::Id>,
736    ) -> Result<Self::Id, LazyWorkspaceError> {
737        let overlay = LazyOverlay::Node {
738            path: node.key,
739            priority: node.priority,
740            change: node.value,
741            left: node.left,
742            right: node.right,
743        };
744        let id = overlay.id()?;
745        self.0
746            .put_lazy_overlay(id, overlay)
747            .await
748            .map_err(store_error)?;
749        Ok(id)
750    }
751
752    fn priority(key: &Self::Key) -> u64 {
753        path_priority(key)
754    }
755}
756
757#[derive(Clone)]
758struct ShadowValue {
759    record: Vec<u8>,
760    metadata: Box<crate::WorkspaceMetadata>,
761}
762
763struct ShadowTreap<'a, S>(&'a S);
764
765#[async_trait]
766impl<S: LazyWorkspaceStore> ImmutableTreap for ShadowTreap<'_, S> {
767    type Id = LazyShadowId;
768    type Key = FileId;
769    type Value = ShadowValue;
770
771    async fn load(
772        &self,
773        id: Self::Id,
774    ) -> Result<Option<ImmutableTreapNode<Self::Key, Self::Value, Self::Id>>, LazyWorkspaceError>
775    {
776        match self.0.load_lazy_shadow(id).await.map_err(store_error)? {
777            Some(LazyShadow::Empty) => Ok(None),
778            Some(LazyShadow::Node {
779                file_id,
780                priority,
781                record,
782                metadata,
783                left,
784                right,
785            }) => Ok(Some(ImmutableTreapNode {
786                key: file_id,
787                priority,
788                value: ShadowValue { record, metadata },
789                left,
790                right,
791            })),
792            None => Err(LazyWorkspaceError::Store(
793                "lazy identity shadow is absent".to_owned(),
794            )),
795        }
796    }
797
798    async fn store(
799        &self,
800        node: ImmutableTreapNode<Self::Key, Self::Value, Self::Id>,
801    ) -> Result<Self::Id, LazyWorkspaceError> {
802        let shadow = LazyShadow::Node {
803            file_id: node.key,
804            priority: node.priority,
805            record: node.value.record,
806            metadata: node.value.metadata,
807            left: node.left,
808            right: node.right,
809        };
810        let id = shadow.id()?;
811        self.0
812            .put_lazy_shadow(id, shadow)
813            .await
814            .map_err(store_error)?;
815        Ok(id)
816    }
817
818    fn priority(key: &Self::Key) -> u64 {
819        shadow_priority(*key)
820    }
821}
822
823/// A source-backed workspace whose unresolved paths remain outside its authored generation.
824pub struct LazyWorkspace<A, O, D, S> {
825    workspace: Workspace<A, O>,
826    source: Arc<D>,
827    store: S,
828}
829
830impl<A, O, D, S> Clone for LazyWorkspace<A, O, D, S>
831where
832    S: Clone,
833{
834    fn clone(&self) -> Self {
835        Self {
836            workspace: self.workspace.clone(),
837            source: Arc::clone(&self.source),
838            store: self.store.clone(),
839        }
840    }
841}
842
843/// Path facts returned without forcing unrelated source state.
844#[derive(Clone, Copy, Debug, Eq, PartialEq)]
845pub struct LazyStat {
846    /// Observed kind.
847    pub kind: SourceNodeKind,
848    /// Observed logical bytes when meaningful.
849    pub logical_bytes: Option<u64>,
850    /// Whether the authored checkout overrides the source.
851    pub authored: bool,
852}
853
854/// Complete facts returned from either authored state or a source observation.
855#[derive(Clone, Debug, Eq, PartialEq)]
856pub enum LazyLookup {
857    /// Authenticated authored state overrides the source.
858    Authored {
859        /// Existing authored binding used to serve this path. This may differ
860        /// from the requested source alias until that alias is promoted.
861        path: String,
862        /// Authenticated authored facts for the shared identity.
863        stat: WorkspaceStat,
864    },
865    /// Latest authored state retained after the final promoted binding was removed.
866    Shadow {
867        /// Path-independent immutable file record.
868        record: FileRecord,
869        /// Projected scalar metadata retained with the record.
870        metadata: crate::WorkspaceMetadata,
871        /// Source-native alias count evidence.
872        source_link_count: u64,
873    },
874    /// Immutable first-observation source state.
875    Source(SourceNode),
876}
877
878struct ResolvedLazyLookup {
879    lookup: LazyLookup,
880    source: Option<SourceReference>,
881}
882
883struct PinnedSourceNode {
884    source: SourceReference,
885    node: SourceNode,
886}
887
888/// Streams one version-pinned demanded file into the SDK blob builder while
889/// preserving the demand provider's exact work receipt.
890struct DemandBlobSource<'a, D> {
891    provider: &'a D,
892    source: SourceReference,
893    path: NamespacePath,
894    version: SourceVersion,
895    offset: u64,
896    length: u64,
897    work: Option<WorkCounters>,
898    failure: Option<DemandError>,
899}
900
901impl<D: DemandSource> crate::kernel::AsyncBlobSource for DemandBlobSource<'_, D> {
902    async fn read<'a>(
903        &'a mut self,
904        destination: &'a mut [u8],
905        cancellation: &'a CancellationToken,
906    ) -> std::io::Result<usize> {
907        let remaining = self.length.saturating_sub(self.offset);
908        if remaining == 0 {
909            self.work = Some(WorkCounters::default());
910            return Ok(0);
911        }
912        let requested = remaining.min(u64::try_from(destination.len()).unwrap_or(u64::MAX));
913        let receipt = self
914            .provider
915            .read_range(
916                self.source,
917                &self.path,
918                self.version,
919                self.offset,
920                requested,
921                cancellation,
922            )
923            .await;
924        let receipt = match receipt {
925            Ok(receipt) => receipt,
926            Err(failure) => {
927                self.work = Some(*failure.work);
928                self.failure = Some(failure.error);
929                return Err(std::io::Error::other("version-pinned demand read failed"));
930            }
931        };
932        self.work = Some(receipt.work);
933        let chunk = receipt.value;
934        if chunk.is_empty() || u64::try_from(chunk.len()).unwrap_or(u64::MAX) > requested {
935            self.failure = Some(DemandError::StaleVersion);
936            return Err(std::io::Error::other(
937                "version-pinned demand read was incomplete",
938            ));
939        }
940        self.offset = self
941            .offset
942            .checked_add(u64::try_from(chunk.len()).unwrap_or(u64::MAX))
943            .ok_or_else(|| std::io::Error::other("demand source offset overflow"))?;
944        destination
945            .get_mut(..chunk.len())
946            .ok_or_else(|| std::io::Error::other("demand source exceeded read bound"))?
947            .copy_from_slice(&chunk);
948        Ok(chunk.len())
949    }
950
951    fn take_work(&mut self) -> Option<WorkCounters> {
952        self.work.take()
953    }
954}
955
956/// Sparse seek target for a lazily sourced regular file.
957#[derive(Clone, Copy, Debug, Eq, PartialEq)]
958pub enum LazySeekTarget {
959    /// First represented data byte.
960    Data,
961    /// First hole byte, including the logical end of a dense source file.
962    Hole,
963}
964
965/// One merged directory entry.
966#[derive(Clone, Debug, Eq, PartialEq)]
967pub struct LazyDirectoryEntry {
968    /// Exact profile-aware child name.
969    pub name: LogicalName,
970    /// Observed or authored kind.
971    pub kind: SourceNodeKind,
972    /// Whether the authored checkout overrides the source.
973    pub authored: bool,
974}
975
976/// Opaque bounded continuation for a merged source/authored directory scan.
977#[derive(Clone, Debug, Eq, PartialEq)]
978pub struct LazyDirectoryCursor {
979    source: SourceReference,
980    overlay: LazyOverlayId,
981    directory: NamespacePath,
982    phase: LazyDirectoryPhase,
983}
984
985#[derive(Clone, Debug, Eq, PartialEq)]
986enum LazyDirectoryPhase {
987    Source(Option<SourceCursor>),
988    Authored(Option<LogicalName>),
989}
990
991/// One bounded page from the merged lazy directory view.
992#[derive(Clone, Debug, Eq, PartialEq)]
993pub struct LazyDirectoryPage {
994    /// At most the requested number of visible entries.
995    pub entries: Vec<LazyDirectoryEntry>,
996    /// Opaque continuation, absent after the authored phase is exhausted.
997    pub next: Option<LazyDirectoryCursor>,
998}
999
1000/// Fail-closed lazy workspace errors.
1001#[derive(Debug, Error)]
1002pub enum LazyWorkspaceError {
1003    /// Authenticated workspace operation failed.
1004    #[error("workspace operation failed: {0}")]
1005    Workspace(String),
1006    /// Demand provider operation failed.
1007    #[error("source demand failed: {0}")]
1008    Demand(#[from] DemandError),
1009    /// Companion state failed.
1010    #[error("lazy state failed: {0}")]
1011    Store(String),
1012    /// Durable state changed too frequently to complete a bounded retry.
1013    #[error("lazy state changed concurrently")]
1014    Concurrent,
1015    /// Provider identity or invalidation epoch no longer matches the binding.
1016    #[error("lazy source binding is stale")]
1017    StaleSource,
1018    /// Requested path is absent after applying authored tombstones.
1019    #[error("path is absent")]
1020    NotFound,
1021    /// Requested subtree root is not a directory.
1022    #[error("path is not a directory")]
1023    NotDirectory,
1024    /// Requested operation requires a regular file.
1025    #[error("path is not a regular file")]
1026    NotRegularFile,
1027    /// Requested read exceeds the explicit bound.
1028    #[error("file exceeds the requested read bound")]
1029    TooLarge,
1030    /// A directory page must request at least one entry.
1031    #[error("directory page bound must be positive")]
1032    InvalidPageBound,
1033    /// Directory view changed after the supplied cursor was issued.
1034    #[error("lazy directory cursor is stale")]
1035    StaleCursor,
1036    /// The path no longer names the object the caller resolved.
1037    #[error("lazy filesystem object identity is stale")]
1038    StaleIdentity,
1039    /// The source fact cannot be represented by this workspace profile.
1040    #[error("source node cannot be represented by the authored workspace")]
1041    UnsupportedNode,
1042    /// Removing the last promoted binding would resurrect stale source bytes
1043    /// through an unresolved hard-link alias.
1044    #[error("source hard-link aliases must be promoted before removing the final authored binding")]
1045    UnresolvedHardLinks,
1046    /// Exact traversal was cancelled before every path was captured.
1047    #[error("lazy exact scan was cancelled")]
1048    Cancelled,
1049    /// Exact traversal exceeded an explicit work budget.
1050    #[error("lazy exact scan exceeded its work budget: {0}")]
1051    Work(String),
1052}
1053
1054#[derive(Clone, Copy)]
1055struct ExactificationSource(SourceReference);
1056
1057impl LazySnapshotRef {
1058    fn exactification_source(
1059        self,
1060        live_source: SourceReference,
1061    ) -> Result<ExactificationSource, LazyWorkspaceError> {
1062        if self.source.identity != live_source.identity {
1063            return Err(LazyWorkspaceError::StaleSource);
1064        }
1065        Ok(ExactificationSource(live_source))
1066    }
1067
1068    fn exactification_state(
1069        self,
1070        workspace_id: WorkspaceId,
1071        parent_workspace_id: WorkspaceId,
1072        source: ExactificationSource,
1073    ) -> LazyWorkspaceState {
1074        LazyWorkspaceState {
1075            schema_version: LAZY_STATE_SCHEMA,
1076            revision: 1,
1077            workspace_id,
1078            parent_workspace_id: Some(parent_workspace_id),
1079            // Observed overlay records retain their pinned source epochs. This
1080            // binding is deliberately live for paths unresolved at capture.
1081            source: source.0,
1082            overlay: self.overlay,
1083            shadows: self.shadows,
1084            pending_remove: None,
1085        }
1086    }
1087}
1088
1089impl<A, O, D, S> LazyWorkspace<A, O, D, S>
1090where
1091    A: AsyncAuthorityStore,
1092    O: AsyncObjectStore,
1093    D: DemandSource + 'static,
1094    S: LazyWorkspaceStore,
1095{
1096    /// Creates an empty authored workspace and binds a source without demanding any path.
1097    pub async fn attach(
1098        fs: &Fs<A, O>,
1099        name: impl AsRef<str>,
1100        source: Arc<D>,
1101        store: S,
1102    ) -> Result<Self, LazyWorkspaceError> {
1103        let workspace = fs.create_workspace(name).await.map_err(workspace_error)?;
1104        Self::attach_workspace(workspace, source, store).await
1105    }
1106
1107    /// Creates an authored workspace with exact filesystem semantics and binds
1108    /// a source without demanding any path.
1109    pub async fn attach_with_config(
1110        fs: &Fs<A, O>,
1111        name: impl AsRef<str>,
1112        source: Arc<D>,
1113        store: S,
1114        config: VolumeConfig,
1115    ) -> Result<Self, LazyWorkspaceError> {
1116        let workspace = fs
1117            .create_workspace_with_config(name, config)
1118            .await
1119            .map_err(workspace_error)?;
1120        Self::attach_workspace(workspace, source, store).await
1121    }
1122
1123    async fn attach_workspace(
1124        workspace: Workspace<A, O>,
1125        source: Arc<D>,
1126        store: S,
1127    ) -> Result<Self, LazyWorkspaceError> {
1128        let overlay = LazyOverlay::default();
1129        let overlay_id = overlay.id()?;
1130        store
1131            .put_lazy_overlay(overlay_id, overlay)
1132            .await
1133            .map_err(store_error)?;
1134        let shadows = LazyShadow::default();
1135        let shadow_id = shadows.id()?;
1136        store
1137            .put_lazy_shadow(shadow_id, shadows)
1138            .await
1139            .map_err(store_error)?;
1140        let state = LazyWorkspaceState {
1141            schema_version: LAZY_STATE_SCHEMA,
1142            revision: 1,
1143            workspace_id: workspace.id(),
1144            parent_workspace_id: None,
1145            source: source.reference(),
1146            overlay: overlay_id,
1147            shadows: shadow_id,
1148            pending_remove: None,
1149        };
1150        let existing = store
1151            .load_lazy_workspace(workspace.id())
1152            .await
1153            .map_err(store_error)?;
1154        match existing {
1155            Some(existing)
1156                if existing.source == state.source
1157                    && existing.schema_version == LAZY_STATE_SCHEMA => {}
1158            Some(_) => return Err(LazyWorkspaceError::StaleSource),
1159            None => {
1160                if !store
1161                    .compare_and_swap_lazy_workspace(workspace.id(), 0, state)
1162                    .await
1163                    .map_err(store_error)?
1164                {
1165                    return Err(LazyWorkspaceError::Concurrent);
1166                }
1167            }
1168        }
1169        let lazy = Self {
1170            workspace,
1171            source,
1172            store,
1173        };
1174        lazy.recover_pending_remove().await?;
1175        Ok(lazy)
1176    }
1177
1178    /// Reopens an existing sparse workspace without demanding source state.
1179    pub async fn open(
1180        workspace: Workspace<A, O>,
1181        source: Arc<D>,
1182        store: S,
1183    ) -> Result<Self, LazyWorkspaceError> {
1184        let state = store
1185            .load_lazy_workspace(workspace.id())
1186            .await
1187            .map_err(store_error)?
1188            .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
1189        if state.schema_version != LAZY_STATE_SCHEMA
1190            || state.workspace_id != workspace.id()
1191            || state.source.identity != source.reference().identity
1192        {
1193            return Err(LazyWorkspaceError::StaleSource);
1194        }
1195        if store
1196            .load_lazy_overlay(state.overlay)
1197            .await
1198            .map_err(store_error)?
1199            .is_none()
1200        {
1201            return Err(LazyWorkspaceError::Store(
1202                "lazy overlay is absent".to_owned(),
1203            ));
1204        }
1205        if store
1206            .load_lazy_shadow(state.shadows)
1207            .await
1208            .map_err(store_error)?
1209            .is_none()
1210        {
1211            return Err(LazyWorkspaceError::Store(
1212                "lazy identity shadow is absent".to_owned(),
1213            ));
1214        }
1215        let lazy = Self {
1216            workspace,
1217            source,
1218            store,
1219        };
1220        lazy.recover_pending_remove().await?;
1221        lazy.rebind_source().await?;
1222        Ok(lazy)
1223    }
1224
1225    /// Authored SDK workspace used by merge, lineage, and publication primitives.
1226    #[must_use]
1227    pub const fn workspace(&self) -> &Workspace<A, O> {
1228        &self.workspace
1229    }
1230
1231    /// Reopens another workspace bound to the same source capability.
1232    ///
1233    /// This is the constant-size branch/resume primitive for adapters that
1234    /// already resolved the destination workspace through durable lineage.
1235    pub async fn open_related(&self, workspace: Workspace<A, O>) -> Result<Self, LazyWorkspaceError>
1236    where
1237        S: Clone,
1238    {
1239        Self::open(workspace, Arc::clone(&self.source), self.store.clone()).await
1240    }
1241
1242    /// Captures the complete logical lazy-tree identity without enumeration.
1243    pub async fn snapshot(&self) -> Result<LazySnapshotRef, LazyWorkspaceError> {
1244        let state = self.state().await?;
1245        let authored_generation = self.workspace.head().await.map_err(workspace_error)?.id();
1246        Ok(LazySnapshotRef::new(
1247            self.workspace.id(),
1248            authored_generation,
1249            state.source,
1250            state.overlay,
1251            state.shadows,
1252        ))
1253    }
1254
1255    /// Loads the constant-size durable source/lineage binding.
1256    pub async fn binding(&self) -> Result<LazyWorkspaceState, LazyWorkspaceError> {
1257        self.state().await
1258    }
1259
1260    /// Forks in constant-size metadata while retaining the exact overlay snapshot.
1261    pub async fn fork(
1262        &self,
1263        destination: impl AsRef<str>,
1264        idempotency_key: IdempotencyKey,
1265    ) -> Result<Self, LazyWorkspaceError> {
1266        let parent = self.state().await?;
1267        let generation = self.workspace.head().await.map_err(workspace_error)?;
1268        let workspace = self
1269            .workspace
1270            .fork(
1271                destination,
1272                ForkOptions {
1273                    generation,
1274                    idempotency_key,
1275                },
1276            )
1277            .await
1278            .map_err(workspace_error)?;
1279        let state = LazyWorkspaceState {
1280            schema_version: LAZY_STATE_SCHEMA,
1281            revision: 1,
1282            workspace_id: workspace.id(),
1283            parent_workspace_id: Some(self.workspace.id()),
1284            source: parent.source,
1285            overlay: parent.overlay,
1286            shadows: parent.shadows,
1287            pending_remove: None,
1288        };
1289        if !self
1290            .store
1291            .compare_and_swap_lazy_workspace(workspace.id(), 0, state)
1292            .await
1293            .map_err(store_error)?
1294        {
1295            let existing = self
1296                .store
1297                .load_lazy_workspace(workspace.id())
1298                .await
1299                .map_err(store_error)?
1300                .ok_or(LazyWorkspaceError::Concurrent)?;
1301            if existing.parent_workspace_id != Some(self.workspace.id())
1302                || existing.source != parent.source
1303                || existing.overlay != parent.overlay
1304                || existing.shadows != parent.shadows
1305            {
1306                return Err(LazyWorkspaceError::Concurrent);
1307            }
1308        }
1309        Ok(Self {
1310            workspace,
1311            source: Arc::clone(&self.source),
1312            store: self.store.clone(),
1313        })
1314    }
1315
1316    /// Materializes an immutable lazy snapshot in a deterministic scratch fork.
1317    ///
1318    /// Previously observed records remain pinned to the snapshot's source epoch;
1319    /// unresolved paths are observed from the injected live source when first
1320    /// demanded. The owning workspace is never moved or rewritten.
1321    #[allow(clippy::too_many_lines)]
1322    pub async fn exactify_snapshot(
1323        &self,
1324        snapshot: LazySnapshotRef,
1325        destination: impl AsRef<str>,
1326        idempotency_key: IdempotencyKey,
1327        budget: WorkBudget,
1328        cancellation: &CancellationToken,
1329    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
1330        cancellation
1331            .check()
1332            .map_err(|_| LazyWorkspaceError::Cancelled)?;
1333        if snapshot.workspace_id != self.workspace.id() {
1334            return Err(LazyWorkspaceError::StaleSource);
1335        }
1336        let exactification_source = snapshot.exactification_source(self.source.reference())?;
1337        let overlay = self
1338            .store
1339            .load_lazy_overlay_measured(snapshot.overlay, budget, cancellation)
1340            .await?;
1341        let mut work = overlay.work;
1342        let shadow = self
1343            .store
1344            .load_lazy_shadow_measured(
1345                snapshot.shadows,
1346                remaining_work(work, budget)?,
1347                cancellation,
1348            )
1349            .await?;
1350        work = account_work(work, shadow.work, budget)?;
1351        if overlay.value.is_none() || shadow.value.is_none() {
1352            return Err(LazyWorkspaceError::Store(
1353                "lazy snapshot index is absent".to_owned(),
1354            ));
1355        }
1356        let generation = self
1357            .workspace
1358            .generation_measured(
1359                snapshot.authored_generation,
1360                remaining_work(work, budget)?,
1361                cancellation,
1362            )
1363            .await
1364            .map_err(workspace_error)?;
1365        work = account_work(work, generation.work, budget)?;
1366        let workspace = self
1367            .workspace
1368            .fork_measured(
1369                destination,
1370                ForkOptions {
1371                    generation: generation.value,
1372                    idempotency_key,
1373                },
1374                remaining_work(work, budget)?,
1375                cancellation,
1376            )
1377            .await
1378            .map_err(workspace_error)?;
1379        work = account_work(work, workspace.work, budget)?;
1380        let workspace = workspace.value;
1381        let state = snapshot.exactification_state(
1382            workspace.id(),
1383            self.workspace.id(),
1384            exactification_source,
1385        );
1386        let inserted = self
1387            .store
1388            .compare_and_swap_lazy_workspace_measured(
1389                workspace.id(),
1390                0,
1391                state.clone(),
1392                remaining_work(work, budget)?,
1393                cancellation,
1394            )
1395            .await?;
1396        work = account_work(work, inserted.work, budget)?;
1397        if !inserted.value {
1398            let existing = self
1399                .store
1400                .load_lazy_workspace_measured(
1401                    workspace.id(),
1402                    remaining_work(work, budget)?,
1403                    cancellation,
1404                )
1405                .await?;
1406            work = account_work(work, existing.work, budget)?;
1407            let existing = existing.value.ok_or(LazyWorkspaceError::Concurrent)?;
1408            if existing.parent_workspace_id != state.parent_workspace_id
1409                || existing.source != state.source
1410                || existing.overlay != state.overlay
1411                || existing.shadows != state.shadows
1412            {
1413                return Err(LazyWorkspaceError::Concurrent);
1414            }
1415        }
1416        let exactified = Self {
1417            workspace,
1418            source: Arc::clone(&self.source),
1419            store: self.store.clone(),
1420        }
1421        .exactify(remaining_work(work, budget)?, cancellation)
1422        .await?;
1423        work = account_work(work, exactified.work, budget)?;
1424        Ok(OperationReceipt {
1425            value: exactified.value,
1426            work,
1427        })
1428    }
1429
1430    /// Advances this binding to the provider's current invalidation epoch
1431    /// without enumerating the source. Prior overlay roots remain immutable;
1432    /// observations are refreshed on demand in the new epoch while authored
1433    /// state and tombstones remain shared.
1434    pub async fn rebind_source(&self) -> Result<LazyWorkspaceState, LazyWorkspaceError> {
1435        let source = self.source.reference();
1436        for _ in 0..MAXIMUM_STATE_RETRIES {
1437            let state = self
1438                .store
1439                .load_lazy_workspace(self.workspace.id())
1440                .await
1441                .map_err(store_error)?
1442                .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
1443            if state.schema_version != LAZY_STATE_SCHEMA
1444                || state.workspace_id != self.workspace.id()
1445                || state.source.identity != source.identity
1446            {
1447                return Err(LazyWorkspaceError::StaleSource);
1448            }
1449            if state.pending_remove.is_some() {
1450                return Err(LazyWorkspaceError::Concurrent);
1451            }
1452            if state.source == source {
1453                return Ok(state);
1454            }
1455            let replacement = LazyWorkspaceState {
1456                revision: state.revision.saturating_add(1),
1457                source,
1458                ..state.clone()
1459            };
1460            if self
1461                .store
1462                .compare_and_swap_lazy_workspace(
1463                    self.workspace.id(),
1464                    state.revision,
1465                    replacement.clone(),
1466                )
1467                .await
1468                .map_err(store_error)?
1469            {
1470                return Ok(replacement);
1471            }
1472        }
1473        Err(LazyWorkspaceError::Concurrent)
1474    }
1475
1476    /// Observes one path without reading file content.
1477    pub async fn stat(&self, path: &str) -> Result<LazyStat, LazyWorkspaceError> {
1478        let cancellation = CancellationToken::new();
1479        self.stat_measured(path, WorkBudget::UNBOUNDED, &cancellation)
1480            .await
1481            .map(|receipt| receipt.value)
1482    }
1483
1484    async fn stat_measured(
1485        &self,
1486        path: &str,
1487        budget: WorkBudget,
1488        cancellation: &CancellationToken,
1489    ) -> Result<OperationReceipt<LazyStat>, LazyWorkspaceError> {
1490        let receipt = self.lookup_measured(path, budget, cancellation).await?;
1491        let value = match receipt.value.lookup {
1492            LazyLookup::Authored { stat, .. } => LazyStat {
1493                kind: source_kind(stat.kind),
1494                logical_bytes: stat.logical_bytes,
1495                authored: true,
1496            },
1497            LazyLookup::Shadow { record, .. } => LazyStat {
1498                kind: source_kind(record.kind),
1499                logical_bytes: record_logical_bytes(record),
1500                authored: true,
1501            },
1502            LazyLookup::Source(node) => LazyStat {
1503                kind: node.kind,
1504                logical_bytes: node.logical_bytes,
1505                authored: false,
1506            },
1507        };
1508        Ok(OperationReceipt {
1509            value,
1510            work: receipt.work,
1511        })
1512    }
1513
1514    /// Returns complete demanded facts without reading regular-file content.
1515    pub async fn lookup(&self, path: &str) -> Result<LazyLookup, LazyWorkspaceError> {
1516        let cancellation = CancellationToken::new();
1517        self.lookup_measured(path, WorkBudget::UNBOUNDED, &cancellation)
1518            .await
1519            .map(|receipt| receipt.value.lookup)
1520    }
1521
1522    #[cfg(all(feature = "native-mount", not(unix)))]
1523    pub(crate) async fn lookup_resolved(
1524        &self,
1525        path: &str,
1526    ) -> Result<(LazyLookup, Option<SourceReference>), LazyWorkspaceError> {
1527        let cancellation = CancellationToken::new();
1528        self.lookup_measured(path, WorkBudget::UNBOUNDED, &cancellation)
1529            .await
1530            .map(|receipt| (receipt.value.lookup, receipt.value.source))
1531    }
1532
1533    async fn lookup_measured(
1534        &self,
1535        path: &str,
1536        budget: WorkBudget,
1537        cancellation: &CancellationToken,
1538    ) -> Result<OperationReceipt<ResolvedLazyLookup>, LazyWorkspaceError> {
1539        let authored = self
1540            .workspace
1541            .stat_optional_measured(path, budget, cancellation)
1542            .await
1543            .map_err(workspace_error)?;
1544        let mut work = authored.work;
1545        if let Some(stat) = authored.value {
1546            return Ok(OperationReceipt {
1547                value: ResolvedLazyLookup {
1548                    lookup: LazyLookup::Authored {
1549                        path: self.canonical_path(path)?,
1550                        stat,
1551                    },
1552                    source: None,
1553                },
1554                work,
1555            });
1556        }
1557        let state_receipt = self
1558            .state_measured(remaining_work(work, budget)?, cancellation)
1559            .await?;
1560        work = account_work(work, state_receipt.work, budget)?;
1561        let state = state_receipt.value;
1562        let tombstoned = self
1563            .tombstoned_measured(
1564                state.overlay,
1565                path,
1566                remaining_work(work, budget)?,
1567                cancellation,
1568            )
1569            .await?;
1570        work = account_work(work, tombstoned.work, budget)?;
1571        if tombstoned.value {
1572            return Err(LazyWorkspaceError::NotFound);
1573        }
1574        let observed = self.observe_measured(path, state, cancellation).await?;
1575        work = account_work(work, observed.work, budget)?;
1576        let (source, node) = observed.value;
1577        let authored = self
1578            .authored_alias_measured(&node, remaining_work(work, budget)?, cancellation)
1579            .await?;
1580        work = account_work(work, authored.work, budget)?;
1581        if let Some(authored) = authored.value {
1582            return Ok(OperationReceipt {
1583                value: ResolvedLazyLookup {
1584                    lookup: authored,
1585                    source: None,
1586                },
1587                work,
1588            });
1589        }
1590        Ok(OperationReceipt {
1591            value: ResolvedLazyLookup {
1592                lookup: LazyLookup::Source(node),
1593                source: Some(source),
1594            },
1595            work,
1596        })
1597    }
1598
1599    /// Returns current facts without extending the immutable observation index.
1600    ///
1601    /// Directory projection uses this after receiving a source page so that
1602    /// gathering entry metadata cannot invalidate the page's continuation.
1603    pub async fn inspect(&self, path: &str) -> Result<LazyLookup, LazyWorkspaceError> {
1604        let cancellation = CancellationToken::new();
1605        self.inspect_measured(path, WorkBudget::UNBOUNDED, &cancellation)
1606            .await
1607            .map(|receipt| receipt.value.lookup)
1608    }
1609
1610    #[cfg(any(unix, test))]
1611    pub(crate) async fn inspect_resolved(
1612        &self,
1613        path: &str,
1614    ) -> Result<(LazyLookup, Option<SourceReference>), LazyWorkspaceError> {
1615        let cancellation = CancellationToken::new();
1616        self.inspect_measured(path, WorkBudget::UNBOUNDED, &cancellation)
1617            .await
1618            .map(|receipt| (receipt.value.lookup, receipt.value.source))
1619    }
1620
1621    async fn inspect_measured(
1622        &self,
1623        path: &str,
1624        budget: WorkBudget,
1625        cancellation: &CancellationToken,
1626    ) -> Result<OperationReceipt<ResolvedLazyLookup>, LazyWorkspaceError> {
1627        self.inspect_with_observation_policy(path, budget, cancellation, false, false)
1628            .await
1629    }
1630
1631    async fn inspect_snapshot_measured(
1632        &self,
1633        path: &str,
1634        budget: WorkBudget,
1635        cancellation: &CancellationToken,
1636    ) -> Result<OperationReceipt<ResolvedLazyLookup>, LazyWorkspaceError> {
1637        self.inspect_with_observation_policy(path, budget, cancellation, true, false)
1638            .await
1639    }
1640
1641    async fn inspect_snapshot_after_authored_absence(
1642        &self,
1643        path: &str,
1644        budget: WorkBudget,
1645        cancellation: &CancellationToken,
1646    ) -> Result<OperationReceipt<ResolvedLazyLookup>, LazyWorkspaceError> {
1647        self.inspect_with_observation_policy(path, budget, cancellation, true, true)
1648            .await
1649    }
1650
1651    async fn inspect_with_observation_policy(
1652        &self,
1653        path: &str,
1654        budget: WorkBudget,
1655        cancellation: &CancellationToken,
1656        retain_pinned_observation: bool,
1657        authored_absent: bool,
1658    ) -> Result<OperationReceipt<ResolvedLazyLookup>, LazyWorkspaceError> {
1659        let authored = if authored_absent {
1660            OperationReceipt {
1661                value: None,
1662                work: WorkCounters::default(),
1663            }
1664        } else {
1665            self.workspace
1666                .stat_optional_measured(path, budget, cancellation)
1667                .await
1668                .map_err(workspace_error)?
1669        };
1670        let mut work = authored.work;
1671        if let Some(stat) = authored.value {
1672            return Ok(OperationReceipt {
1673                value: ResolvedLazyLookup {
1674                    lookup: LazyLookup::Authored {
1675                        path: self.canonical_path(path)?,
1676                        stat,
1677                    },
1678                    source: None,
1679                },
1680                work,
1681            });
1682        }
1683        let state_receipt = self
1684            .state_measured(remaining_work(work, budget)?, cancellation)
1685            .await?;
1686        work = account_work(work, state_receipt.work, budget)?;
1687        let state = state_receipt.value;
1688        let fact = self
1689            .overlay_fact_measured(
1690                state.overlay,
1691                path,
1692                remaining_work(work, budget)?,
1693                cancellation,
1694            )
1695            .await?;
1696        work = account_work(work, fact.work, budget)?;
1697        let observed = match fact.value {
1698            Some(LazyOverlayChange::Tombstone) => return Err(LazyWorkspaceError::NotFound),
1699            Some(LazyOverlayChange::Observe { source, node })
1700                if retain_pinned_observation || source == state.source =>
1701            {
1702                Some((source, node))
1703            }
1704            Some(LazyOverlayChange::Observe { .. }) | None => None,
1705        };
1706        let (source, node) = if let Some(observed) = observed {
1707            observed
1708        } else {
1709            let receipt = self
1710                .source
1711                .lookup(state.source, &self.namespace_path(path)?, cancellation)
1712                .await
1713                .map_err(|failure| LazyWorkspaceError::from(failure.error))?;
1714            work = account_work(work, receipt.work, budget)?;
1715            (
1716                state.source,
1717                receipt.value.ok_or(LazyWorkspaceError::NotFound)?,
1718            )
1719        };
1720        let tombstoned = self
1721            .tombstoned_measured(
1722                state.overlay,
1723                path,
1724                remaining_work(work, budget)?,
1725                cancellation,
1726            )
1727            .await?;
1728        work = account_work(work, tombstoned.work, budget)?;
1729        if tombstoned.value {
1730            return Err(LazyWorkspaceError::NotFound);
1731        }
1732        let authored = self
1733            .authored_alias_measured(&node, remaining_work(work, budget)?, cancellation)
1734            .await?;
1735        work = account_work(work, authored.work, budget)?;
1736        if let Some(authored) = authored.value {
1737            return Ok(OperationReceipt {
1738                value: ResolvedLazyLookup {
1739                    lookup: authored,
1740                    source: None,
1741                },
1742                work,
1743            });
1744        }
1745        Ok(OperationReceipt {
1746            value: ResolvedLazyLookup {
1747                lookup: LazyLookup::Source(node),
1748                source: Some(source),
1749            },
1750            work,
1751        })
1752    }
1753
1754    /// Returns a stable SDK identity for an unresolved source object.
1755    ///
1756    /// Aliases carrying the same source-native identity map to one `FileId`,
1757    /// independent of path, so mount lookups preserve hard-link topology.
1758    pub fn source_file_id(&self, node: &SourceNode) -> FileId {
1759        let mut hasher = blake3::Hasher::new();
1760        hasher.update(b"acyclic-fs-lazy-source-file-id-v1\0");
1761        hasher.update(&self.source.reference().identity);
1762        hasher.update(&node.file_identity);
1763        let digest = hasher.finalize();
1764        let mut bytes = [0_u8; 16];
1765        bytes.copy_from_slice(&digest.as_bytes()[..16]);
1766        FileId::from_bytes(bytes)
1767    }
1768
1769    /// Direct identity of the resolved fact. Mounted directory identity is a
1770    /// separate projection rule in `stable_file_id_for_lookup`.
1771    fn direct_file_id_for_lookup(&self, lookup: &LazyLookup) -> FileId {
1772        match lookup {
1773            LazyLookup::Source(node) => self.source_file_id(node),
1774            LazyLookup::Authored { stat, .. } => stat.file_id,
1775            LazyLookup::Shadow { record, .. } => record.file_id,
1776        }
1777    }
1778
1779    #[cfg(any(feature = "native-mount", test))]
1780    pub(crate) async fn stable_file_id_for_lookup(
1781        &self,
1782        path: &str,
1783        lookup: &LazyLookup,
1784    ) -> Result<FileId, LazyWorkspaceError> {
1785        match lookup {
1786            LazyLookup::Source(node) => Ok(self.source_file_id(node)),
1787            LazyLookup::Shadow { record, .. } => Ok(record.file_id),
1788            LazyLookup::Authored { stat, .. } if stat.kind == FileKind::Directory => {
1789                let state = self.state().await?;
1790                match self.overlay_fact(state.overlay, path).await? {
1791                    Some(LazyOverlayChange::Observe { node, .. })
1792                        if node.kind == SourceNodeKind::Directory =>
1793                    {
1794                        Ok(self.source_file_id(&node))
1795                    }
1796                    Some(LazyOverlayChange::Observe { .. })
1797                    | Some(LazyOverlayChange::Tombstone)
1798                    | None => Ok(stat.file_id),
1799                }
1800            }
1801            LazyLookup::Authored { stat, .. } => Ok(stat.file_id),
1802        }
1803    }
1804
1805    async fn authored_alias_measured(
1806        &self,
1807        node: &SourceNode,
1808        budget: WorkBudget,
1809        cancellation: &CancellationToken,
1810    ) -> Result<OperationReceipt<Option<LazyLookup>>, LazyWorkspaceError> {
1811        // Only source objects with multiple directory bindings can resolve
1812        // through an authored alias. Avoid the generation-wide reverse-path
1813        // query for ordinary files and directories; on large compiler trees
1814        // that otherwise creates one derived path-index entry per stat.
1815        if node.kind == SourceNodeKind::Directory || node.link_count == Some(1) {
1816            return Ok(OperationReceipt {
1817                value: None,
1818                work: WorkCounters::default(),
1819            });
1820        }
1821        let file_id = self.source_file_id(node);
1822        let state = self.state_measured(budget, cancellation).await?;
1823        let generation = self
1824            .workspace
1825            .head_measured(remaining_work(state.work, budget)?, cancellation)
1826            .await
1827            .map_err(workspace_error)?;
1828        let mut work = account_work(state.work, generation.work, budget)?;
1829        let state = state.value;
1830        let paths = generation
1831            .value
1832            .paths_for_file_id_measured(
1833                file_id,
1834                u32::MAX,
1835                remaining_work(work, budget)?,
1836                cancellation,
1837            )
1838            .await
1839            .map_err(workspace_error)?;
1840        work = account_work(work, paths.work, budget)?;
1841        if let Some(path) = paths.value.into_iter().next() {
1842            let stat = self
1843                .workspace
1844                .stat_optional_measured(&path, remaining_work(work, budget)?, cancellation)
1845                .await
1846                .map_err(workspace_error)?;
1847            work = account_work(work, stat.work, budget)?;
1848            let mut stat = stat.value.ok_or(LazyWorkspaceError::Concurrent)?;
1849            if let Some(source_links) = node.link_count {
1850                stat.link_count = stat.link_count.max(source_links);
1851            }
1852            return Ok(OperationReceipt {
1853                value: Some(LazyLookup::Authored { path, stat }),
1854                work,
1855            });
1856        }
1857        let shadow = self
1858            .shadow_record_measured(
1859                state.shadows,
1860                file_id,
1861                remaining_work(work, budget)?,
1862                cancellation,
1863            )
1864            .await?;
1865        work = account_work(work, shadow.work, budget)?;
1866        let Some((record, metadata)) = shadow.value else {
1867            return Ok(OperationReceipt { value: None, work });
1868        };
1869        Ok(OperationReceipt {
1870            value: Some(LazyLookup::Shadow {
1871                record,
1872                metadata,
1873                source_link_count: node.link_count.unwrap_or(1),
1874            }),
1875            work,
1876        })
1877    }
1878
1879    /// Reads one exact range, demanding no unrelated content.
1880    pub async fn read_range(
1881        &self,
1882        path: &str,
1883        offset: u64,
1884        length: u64,
1885    ) -> Result<Bytes, LazyWorkspaceError> {
1886        let cancellation = CancellationToken::new();
1887        self.read_range_measured(path, offset, length, WorkBudget::UNBOUNDED, &cancellation)
1888            .await
1889            .map(|receipt| receipt.value)
1890    }
1891
1892    #[cfg(any(unix, test))]
1893    pub(crate) async fn read_source_range(
1894        &self,
1895        path: &str,
1896        source: SourceReference,
1897        node: SourceNode,
1898        offset: u64,
1899        length: u64,
1900    ) -> Result<Bytes, LazyWorkspaceError> {
1901        if node.kind != SourceNodeKind::RegularFile {
1902            return Err(LazyWorkspaceError::NotRegularFile);
1903        }
1904        self.source
1905            .read_range(
1906                source,
1907                &self.namespace_path(path)?,
1908                node.version,
1909                offset,
1910                length,
1911                &CancellationToken::new(),
1912            )
1913            .await
1914            .map(|receipt| receipt.value)
1915            .map_err(|failure| LazyWorkspaceError::from(failure.error))
1916    }
1917
1918    async fn read_range_measured(
1919        &self,
1920        path: &str,
1921        offset: u64,
1922        length: u64,
1923        budget: WorkBudget,
1924        cancellation: &CancellationToken,
1925    ) -> Result<OperationReceipt<Bytes>, LazyWorkspaceError> {
1926        let lookup = self.lookup_measured(path, budget, cancellation).await?;
1927        let prior = lookup.work;
1928        let ResolvedLazyLookup { lookup, source } = lookup.value;
1929        match lookup {
1930            LazyLookup::Authored {
1931                path: authored_path,
1932                ..
1933            } => {
1934                let value = self
1935                    .workspace
1936                    .read_range(&authored_path, offset, length)
1937                    .await
1938                    .map_err(workspace_error)?;
1939                Ok(OperationReceipt { value, work: prior })
1940            }
1941            LazyLookup::Shadow { record, .. } => {
1942                if record.kind != FileKind::Regular {
1943                    return Err(LazyWorkspaceError::NotRegularFile);
1944                }
1945                let receipt = self
1946                    .workspace
1947                    .detached_record(record)
1948                    .read_range(
1949                        crate::ByteRange { offset, length },
1950                        remaining_work(prior, budget)?,
1951                        cancellation,
1952                    )
1953                    .await
1954                    .map_err(|failure| LazyWorkspaceError::Workspace(failure.error.to_string()));
1955                let receipt = receipt?;
1956                Ok(OperationReceipt {
1957                    value: receipt.value.bytes,
1958                    work: account_work(prior, receipt.work, budget)?,
1959                })
1960            }
1961            LazyLookup::Source(node) => {
1962                if node.kind != SourceNodeKind::RegularFile {
1963                    return Err(LazyWorkspaceError::NotRegularFile);
1964                }
1965                let source = source.ok_or(LazyWorkspaceError::Concurrent)?;
1966                let receipt = self
1967                    .source
1968                    .read_range(
1969                        source,
1970                        &self.namespace_path(path)?,
1971                        node.version,
1972                        offset,
1973                        length,
1974                        cancellation,
1975                    )
1976                    .await
1977                    .map_err(|failure| LazyWorkspaceError::from(failure.error))?;
1978                Ok(OperationReceipt {
1979                    value: receipt.value,
1980                    work: account_work(prior, receipt.work, budget)?,
1981                })
1982            }
1983        }
1984    }
1985
1986    /// Reads a complete regular file under an explicit byte bound.
1987    pub async fn read(&self, path: &str, maximum_bytes: u64) -> Result<Bytes, LazyWorkspaceError> {
1988        let stat = self.stat(path).await?;
1989        let length = stat
1990            .logical_bytes
1991            .ok_or(LazyWorkspaceError::NotRegularFile)?;
1992        if length > maximum_bytes {
1993            return Err(LazyWorkspaceError::TooLarge);
1994        }
1995        self.read_range(path, 0, length).await
1996    }
1997
1998    /// Reads one exact symbolic-link target without following it.
1999    pub async fn read_link(&self, path: &str) -> Result<Bytes, LazyWorkspaceError> {
2000        let cancellation = CancellationToken::new();
2001        self.read_link_measured(path, WorkBudget::UNBOUNDED, &cancellation)
2002            .await
2003            .map(|receipt| receipt.value)
2004    }
2005
2006    async fn read_link_measured(
2007        &self,
2008        path: &str,
2009        budget: WorkBudget,
2010        cancellation: &CancellationToken,
2011    ) -> Result<OperationReceipt<Bytes>, LazyWorkspaceError> {
2012        let lookup = self.lookup_measured(path, budget, cancellation).await?;
2013        let prior = lookup.work;
2014        let ResolvedLazyLookup { lookup, source } = lookup.value;
2015        match lookup {
2016            LazyLookup::Authored {
2017                path: authored_path,
2018                stat,
2019            } => {
2020                if stat.kind != FileKind::SymbolicLink {
2021                    return Err(LazyWorkspaceError::NotRegularFile);
2022                }
2023                let value = self
2024                    .workspace
2025                    .read_symbolic_link(&authored_path)
2026                    .await
2027                    .map_err(workspace_error)?;
2028                Ok(OperationReceipt { value, work: prior })
2029            }
2030            LazyLookup::Shadow { record, .. } => {
2031                let receipt = self
2032                    .workspace
2033                    .detached_record(record)
2034                    .read_symbolic_link(remaining_work(prior, budget)?, cancellation)
2035                    .await
2036                    .map_err(|failure| LazyWorkspaceError::Workspace(failure.error.to_string()))?;
2037                Ok(OperationReceipt {
2038                    value: receipt.value,
2039                    work: account_work(prior, receipt.work, budget)?,
2040                })
2041            }
2042            LazyLookup::Source(node) => {
2043                if node.kind != SourceNodeKind::SymbolicLink {
2044                    return Err(LazyWorkspaceError::NotRegularFile);
2045                }
2046                let source = source.ok_or(LazyWorkspaceError::Concurrent)?;
2047                let receipt = self
2048                    .source
2049                    .read_link(
2050                        source,
2051                        &self.namespace_path(path)?,
2052                        node.version,
2053                        cancellation,
2054                    )
2055                    .await
2056                    .map_err(|failure| LazyWorkspaceError::from(failure.error))?;
2057                Ok(OperationReceipt {
2058                    value: receipt.value,
2059                    work: account_work(prior, receipt.work, budget)?,
2060                })
2061            }
2062        }
2063    }
2064
2065    /// Finds a sparse boundary without demanding file contents.
2066    ///
2067    /// Authored files use their authenticated extent tree. Unresolved native
2068    /// sources currently expose only dense length/version facts, so their
2069    /// exact portable representation is data through EOF followed by a hole.
2070    pub async fn seek(
2071        &self,
2072        path: &str,
2073        offset: u64,
2074        target: LazySeekTarget,
2075    ) -> Result<Option<u64>, LazyWorkspaceError> {
2076        match self.lookup(path).await? {
2077            LazyLookup::Authored {
2078                path: authored_path,
2079                stat,
2080            } => {
2081                if stat.kind != FileKind::Regular {
2082                    return Err(LazyWorkspaceError::NotRegularFile);
2083                }
2084                let length = stat
2085                    .logical_bytes
2086                    .ok_or(LazyWorkspaceError::NotRegularFile)?;
2087                if offset >= length {
2088                    return Ok(None);
2089                }
2090                let plan = self
2091                    .workspace
2092                    .plan_extents(&authored_path, offset, length - offset, 65_536)
2093                    .await
2094                    .map_err(workspace_error)?;
2095                let found = plan.spans.into_iter().find(|span| match target {
2096                    LazySeekTarget::Data => span.kind != WorkspaceExtentKind::Hole,
2097                    LazySeekTarget::Hole => span.kind == WorkspaceExtentKind::Hole,
2098                });
2099                Ok(found
2100                    .map(|span| span.offset)
2101                    .or_else(|| (target == LazySeekTarget::Hole).then_some(length)))
2102            }
2103            LazyLookup::Shadow { record, .. } => {
2104                if record.kind != FileKind::Regular {
2105                    return Err(LazyWorkspaceError::NotRegularFile);
2106                }
2107                self.workspace
2108                    .detached_record(record)
2109                    .seek(
2110                        offset,
2111                        match target {
2112                            LazySeekTarget::Data => crate::kernel::ExtentSeekTarget::Data,
2113                            LazySeekTarget::Hole => crate::kernel::ExtentSeekTarget::Hole,
2114                        },
2115                        crate::WorkBudget::UNBOUNDED,
2116                        &crate::CancellationToken::new(),
2117                    )
2118                    .await
2119                    .map(|receipt| receipt.value)
2120                    .map_err(|failure| LazyWorkspaceError::Workspace(failure.error.to_string()))
2121            }
2122            LazyLookup::Source(node) => {
2123                if node.kind != SourceNodeKind::RegularFile {
2124                    return Err(LazyWorkspaceError::NotRegularFile);
2125                }
2126                let length = node
2127                    .logical_bytes
2128                    .ok_or(LazyWorkspaceError::NotRegularFile)?;
2129                if offset >= length {
2130                    return Ok(None);
2131                }
2132                Ok(Some(match target {
2133                    LazySeekTarget::Data => offset,
2134                    LazySeekTarget::Hole => length,
2135                }))
2136            }
2137        }
2138    }
2139
2140    /// Promotes exactly one demanded source node into authored copy-on-write state.
2141    /// Directories remain shallow and regular files are fetched in bounded ranges.
2142    #[allow(clippy::too_many_lines)]
2143    pub async fn promote(
2144        &self,
2145        path: &str,
2146        maximum_bytes: u64,
2147        idempotency_key: IdempotencyKey,
2148    ) -> Result<TransactionCommit<A, O>, LazyWorkspaceError> {
2149        self.promote_with_permit(
2150            path,
2151            maximum_bytes,
2152            idempotency_key,
2153            crate::PublicationPermit::Unrestricted,
2154        )
2155        .await
2156    }
2157
2158    #[allow(clippy::too_many_lines)]
2159    async fn populate_source_node_measured(
2160        &self,
2161        transaction: &mut crate::Transaction<A, O>,
2162        requested: &str,
2163        pinned: &PinnedSourceNode,
2164        maximum_bytes: u64,
2165        budget: WorkBudget,
2166        cancellation: &CancellationToken,
2167    ) -> Result<WorkCounters, LazyWorkspaceError> {
2168        let node = &pinned.node;
2169        let source = pinned.source;
2170        let mut work = WorkCounters::default();
2171        match node.kind {
2172            SourceNodeKind::Directory => {
2173                let applied = transaction
2174                    .create_directory_measured(
2175                        requested,
2176                        remaining_work(work, budget)?,
2177                        cancellation,
2178                    )
2179                    .await
2180                    .map_err(workspace_error)?;
2181                work = account_work(work, applied, budget)?;
2182            }
2183            SourceNodeKind::RegularFile => {
2184                work = self
2185                    .populate_regular_source_node(
2186                        transaction,
2187                        requested,
2188                        pinned,
2189                        maximum_bytes,
2190                        budget,
2191                        cancellation,
2192                    )
2193                    .await?;
2194            }
2195            SourceNodeKind::SymbolicLink => {
2196                let receipt = self
2197                    .source
2198                    .read_link(
2199                        source,
2200                        &self.namespace_path(requested)?,
2201                        node.version,
2202                        cancellation,
2203                    )
2204                    .await
2205                    .map_err(|failure| LazyWorkspaceError::from(failure.error))?;
2206                work = account_work(work, receipt.work, budget)?;
2207                let applied = transaction
2208                    .create_symbolic_link_measured(
2209                        requested,
2210                        receipt.value,
2211                        remaining_work(work, budget)?,
2212                        cancellation,
2213                    )
2214                    .await
2215                    .map_err(workspace_error)?;
2216                work = account_work(work, applied, budget)?;
2217            }
2218            SourceNodeKind::Fifo
2219            | SourceNodeKind::Socket
2220            | SourceNodeKind::CharacterDevice
2221            | SourceNodeKind::BlockDevice => {
2222                let kind = match node.kind {
2223                    SourceNodeKind::Fifo => FileKind::Fifo,
2224                    SourceNodeKind::Socket => FileKind::Socket,
2225                    SourceNodeKind::CharacterDevice => FileKind::CharacterDevice,
2226                    SourceNodeKind::BlockDevice => FileKind::BlockDevice,
2227                    _ => unreachable!(),
2228                };
2229                let applied = transaction
2230                    .create_special_measured(
2231                        requested,
2232                        kind,
2233                        node.device,
2234                        remaining_work(work, budget)?,
2235                        cancellation,
2236                    )
2237                    .await
2238                    .map_err(workspace_error)?;
2239                work = account_work(work, applied, budget)?;
2240            }
2241            SourceNodeKind::Unsupported => return Err(LazyWorkspaceError::UnsupportedNode),
2242        }
2243        let applied = transaction
2244            .set_metadata_measured(
2245                requested,
2246                source_file_metadata(node.metadata),
2247                remaining_work(work, budget)?,
2248                cancellation,
2249            )
2250            .await
2251            .map_err(workspace_error)?;
2252        work = account_work(work, applied, budget)?;
2253        if node.kind != SourceNodeKind::Directory {
2254            let applied = transaction
2255                .preserve_file_identity_measured(
2256                    requested,
2257                    self.source_file_id(node),
2258                    remaining_work(work, budget)?,
2259                    cancellation,
2260                )
2261                .await
2262                .map_err(workspace_error)?;
2263            work = account_work(work, applied, budget)?;
2264        }
2265        Ok(work)
2266    }
2267
2268    async fn populate_regular_source_node(
2269        &self,
2270        transaction: &mut crate::Transaction<A, O>,
2271        requested: &str,
2272        pinned: &PinnedSourceNode,
2273        maximum_bytes: u64,
2274        budget: WorkBudget,
2275        cancellation: &CancellationToken,
2276    ) -> Result<WorkCounters, LazyWorkspaceError> {
2277        let node = &pinned.node;
2278        let source = pinned.source;
2279        let length = node
2280            .logical_bytes
2281            .ok_or(LazyWorkspaceError::NotRegularFile)?;
2282        if length > maximum_bytes {
2283            return Err(LazyWorkspaceError::TooLarge);
2284        }
2285        let applied = transaction
2286            .create_file_measured(requested, Bytes::new(), budget, cancellation)
2287            .await
2288            .map_err(workspace_error)?;
2289        let mut work = account_work(WorkCounters::default(), applied, budget)?;
2290        let mut offset = 0_u64;
2291        while offset < length {
2292            let requested_bytes = (length - offset).min(1024 * 1024);
2293            let receipt = self
2294                .source
2295                .read_range(
2296                    source,
2297                    &self.namespace_path(requested)?,
2298                    node.version,
2299                    offset,
2300                    requested_bytes,
2301                    cancellation,
2302                )
2303                .await
2304                .map_err(|failure| LazyWorkspaceError::from(failure.error))?;
2305            work = account_work(work, receipt.work, budget)?;
2306            let chunk = receipt.value;
2307            if chunk.is_empty() {
2308                return Err(LazyWorkspaceError::Demand(DemandError::StaleVersion));
2309            }
2310            let chunk_length =
2311                u64::try_from(chunk.len()).map_err(|_| LazyWorkspaceError::TooLarge)?;
2312            let applied = transaction
2313                .write_range_measured(
2314                    requested,
2315                    offset,
2316                    chunk,
2317                    remaining_work(work, budget)?,
2318                    cancellation,
2319                )
2320                .await
2321                .map_err(workspace_error)?;
2322            work = account_work(work, applied, budget)?;
2323            offset = offset
2324                .checked_add(chunk_length)
2325                .ok_or(LazyWorkspaceError::TooLarge)?;
2326        }
2327        Ok(work)
2328    }
2329
2330    async fn stage_regular_source_content(
2331        &self,
2332        transaction: &crate::Transaction<A, O>,
2333        requested: &str,
2334        pinned: &PinnedSourceNode,
2335        budget: WorkBudget,
2336        cancellation: &CancellationToken,
2337    ) -> Result<OperationReceipt<crate::StagedContent>, LazyWorkspaceError> {
2338        let length = pinned
2339            .node
2340            .logical_bytes
2341            .ok_or(LazyWorkspaceError::NotRegularFile)?;
2342        let mut source = DemandBlobSource {
2343            provider: self.source.as_ref(),
2344            source: pinned.source,
2345            path: self.namespace_path(requested)?,
2346            version: pinned.node.version,
2347            offset: 0,
2348            length,
2349            work: None,
2350            failure: None,
2351        };
2352        let staged = transaction
2353            .stage_content_measured(&mut source, length.max(1), budget, cancellation)
2354            .await
2355            .map_err(|error| {
2356                source
2357                    .failure
2358                    .take()
2359                    .map_or_else(|| workspace_error(error), LazyWorkspaceError::Demand)
2360            })?;
2361        if staged.value.logical_bytes() != length {
2362            return Err(LazyWorkspaceError::Demand(DemandError::StaleVersion));
2363        }
2364        Ok(staged)
2365    }
2366
2367    async fn commit_promotion(
2368        &self,
2369        mut transaction: crate::Transaction<A, O>,
2370        permit: crate::PublicationPermit,
2371        mut work: WorkCounters,
2372        budget: WorkBudget,
2373        cancellation: &CancellationToken,
2374    ) -> Result<OperationReceipt<TransactionCommit<A, O>>, LazyWorkspaceError> {
2375        let receipt = transaction
2376            .commit_with_permit_measured(permit, remaining_work(work, budget)?, cancellation)
2377            .await
2378            .map_err(workspace_error)?;
2379        work = account_work(work, receipt.work, budget)?;
2380        Ok(OperationReceipt {
2381            value: receipt.value,
2382            work,
2383        })
2384    }
2385
2386    async fn existing_promotion(
2387        &self,
2388        mut work: WorkCounters,
2389        budget: WorkBudget,
2390        cancellation: &CancellationToken,
2391    ) -> Result<OperationReceipt<TransactionCommit<A, O>>, LazyWorkspaceError> {
2392        let head = self
2393            .workspace
2394            .head_measured(remaining_work(work, budget)?, cancellation)
2395            .await
2396            .map_err(workspace_error)?;
2397        work = account_work(work, head.work, budget)?;
2398        Ok(OperationReceipt {
2399            value: TransactionCommit::AlreadyCommitted(head.value),
2400            work,
2401        })
2402    }
2403
2404    async fn commit_exactify_batch(
2405        &self,
2406        transaction: crate::Transaction<A, O>,
2407        permit: crate::PublicationPermit,
2408        budget: WorkBudget,
2409        cancellation: &CancellationToken,
2410    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
2411        let receipt = self
2412            .commit_promotion(
2413                transaction,
2414                permit,
2415                WorkCounters::default(),
2416                budget,
2417                cancellation,
2418            )
2419            .await?;
2420        match receipt.value {
2421            TransactionCommit::Committed(generation)
2422            | TransactionCommit::AlreadyCommitted(generation) => Ok(OperationReceipt {
2423                value: generation,
2424                work: receipt.work,
2425            }),
2426            TransactionCommit::Conflict { .. }
2427            | TransactionCommit::Fenced
2428            | TransactionCommit::IdempotencyConflict => Err(LazyWorkspaceError::Concurrent),
2429        }
2430    }
2431
2432    #[allow(clippy::too_many_arguments)]
2433    async fn flush_exactify_source_batch(
2434        &self,
2435        transaction: &mut crate::Transaction<A, O>,
2436        pending: &mut Vec<AuthoredMutation>,
2437        pending_bytes: &mut u64,
2438        retained_bytes: &mut u64,
2439        work: WorkCounters,
2440        budget: WorkBudget,
2441        cancellation: &CancellationToken,
2442    ) -> Result<WorkCounters, LazyWorkspaceError> {
2443        if pending.is_empty() {
2444            return Ok(work);
2445        }
2446        let operations = std::mem::take(pending);
2447        let applied = transaction
2448            .apply_authored_bulk_measured(operations, remaining_work(work, budget)?, cancellation)
2449            .await
2450            .map_err(workspace_error)?;
2451        let work = account_nested_with_live_memory(work, applied, *retained_bytes, budget)?;
2452        *retained_bytes = retained_bytes
2453            .checked_sub(*pending_bytes)
2454            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
2455        *pending_bytes = 0;
2456        Ok(work)
2457    }
2458
2459    /// Promotes one demanded node under an authority-side publication permit.
2460    pub async fn promote_with_permit(
2461        &self,
2462        path: &str,
2463        maximum_bytes: u64,
2464        idempotency_key: IdempotencyKey,
2465        permit: crate::PublicationPermit,
2466    ) -> Result<TransactionCommit<A, O>, LazyWorkspaceError> {
2467        let cancellation = CancellationToken::new();
2468        self.promote_with_permit_measured(
2469            path,
2470            maximum_bytes,
2471            idempotency_key,
2472            permit,
2473            WorkBudget::UNBOUNDED,
2474            &cancellation,
2475        )
2476        .await
2477        .map(|receipt| receipt.value)
2478    }
2479
2480    #[allow(clippy::too_many_lines)]
2481    async fn promote_with_permit_measured(
2482        &self,
2483        path: &str,
2484        maximum_bytes: u64,
2485        idempotency_key: IdempotencyKey,
2486        permit: crate::PublicationPermit,
2487        budget: WorkBudget,
2488        cancellation: &CancellationToken,
2489    ) -> Result<OperationReceipt<TransactionCommit<A, O>>, LazyWorkspaceError> {
2490        let requested = self.canonical_path(path)?;
2491        let lookup = self
2492            .lookup_measured(&requested, budget, cancellation)
2493            .await?;
2494        self.promote_resolved_with_permit_measured(
2495            &requested,
2496            lookup.value,
2497            maximum_bytes,
2498            idempotency_key,
2499            permit,
2500            lookup.work,
2501            budget,
2502            cancellation,
2503        )
2504        .await
2505    }
2506
2507    #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
2508    async fn promote_resolved_with_permit_measured(
2509        &self,
2510        requested: &str,
2511        lookup: ResolvedLazyLookup,
2512        maximum_bytes: u64,
2513        idempotency_key: IdempotencyKey,
2514        permit: crate::PublicationPermit,
2515        mut work: WorkCounters,
2516        budget: WorkBudget,
2517        cancellation: &CancellationToken,
2518    ) -> Result<OperationReceipt<TransactionCommit<A, O>>, LazyWorkspaceError> {
2519        work.verify(budget)
2520            .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
2521        let transaction = self
2522            .workspace
2523            .begin_transaction_measured(
2524                idempotency_key,
2525                remaining_work(work, budget)?,
2526                cancellation,
2527            )
2528            .await
2529            .map_err(workspace_error)?;
2530        work = account_work(work, transaction.work, budget)?;
2531        let mut transaction = transaction.value;
2532        if let Some(parent) = parent_path(requested) {
2533            let applied = transaction
2534                .create_dir_all_measured(parent, remaining_work(work, budget)?, cancellation)
2535                .await
2536                .map_err(workspace_error)?;
2537            work = account_work(work, applied, budget)?;
2538        }
2539        let applied = self
2540            .apply_resolved_promotion_measured(
2541                &mut transaction,
2542                requested,
2543                lookup,
2544                maximum_bytes,
2545                remaining_work(work, budget)?,
2546                cancellation,
2547            )
2548            .await?;
2549        work = account_work(work, applied.work, budget)?;
2550        if !applied.value {
2551            return self.existing_promotion(work, budget, cancellation).await;
2552        }
2553        self.commit_promotion(transaction, permit, work, budget, cancellation)
2554            .await
2555    }
2556
2557    /// Applies one already-resolved source fact to an unpublished candidate.
2558    /// Callers choose the publication boundary; the source semantics stay here.
2559    #[allow(clippy::too_many_arguments)]
2560    async fn apply_resolved_promotion_measured(
2561        &self,
2562        transaction: &mut crate::Transaction<A, O>,
2563        requested: &str,
2564        lookup: ResolvedLazyLookup,
2565        maximum_bytes: u64,
2566        budget: WorkBudget,
2567        cancellation: &CancellationToken,
2568    ) -> Result<OperationReceipt<bool>, LazyWorkspaceError> {
2569        let mut work = WorkCounters::default();
2570        let ResolvedLazyLookup { lookup, source } = lookup;
2571        let pinned = match lookup {
2572            LazyLookup::Source(node) => PinnedSourceNode {
2573                source: source.ok_or(LazyWorkspaceError::Concurrent)?,
2574                node,
2575            },
2576            LazyLookup::Authored {
2577                path: authored_path,
2578                ..
2579            } => {
2580                if authored_path == requested {
2581                    return Ok(OperationReceipt { value: false, work });
2582                }
2583                let applied = transaction
2584                    .hard_link_measured(
2585                        &authored_path,
2586                        requested,
2587                        remaining_work(work, budget)?,
2588                        cancellation,
2589                    )
2590                    .await
2591                    .map_err(workspace_error)?;
2592                work = account_work(work, applied, budget)?;
2593                return Ok(OperationReceipt { value: true, work });
2594            }
2595            LazyLookup::Shadow { record, .. } => {
2596                let applied = transaction
2597                    .restore_record_measured(
2598                        requested,
2599                        record,
2600                        remaining_work(work, budget)?,
2601                        cancellation,
2602                    )
2603                    .await
2604                    .map_err(workspace_error)?;
2605                work = account_work(work, applied, budget)?;
2606                return Ok(OperationReceipt { value: true, work });
2607            }
2608        };
2609        let populated = self
2610            .populate_source_node_measured(
2611                transaction,
2612                requested,
2613                &pinned,
2614                maximum_bytes,
2615                remaining_work(work, budget)?,
2616                cancellation,
2617            )
2618            .await?;
2619        work = account_work(work, populated, budget)?;
2620        Ok(OperationReceipt { value: true, work })
2621    }
2622
2623    /// Promotes one source node and accepts only a durable successful outcome.
2624    ///
2625    /// Mount adapters use this boundary so a typed conflict can never advance
2626    /// their authored view as though promotion succeeded.
2627    pub async fn promote_exact(
2628        &self,
2629        path: &str,
2630        expected_source: FileId,
2631        maximum_bytes: u64,
2632        idempotency_key: IdempotencyKey,
2633    ) -> Result<crate::Generation<A, O>, LazyWorkspaceError> {
2634        let requested = self.canonical_path(path)?;
2635        let lookup = self.lookup(&requested).await?;
2636        if self.direct_file_id_for_lookup(&lookup) != expected_source {
2637            return Err(LazyWorkspaceError::StaleIdentity);
2638        }
2639        if let LazyLookup::Authored { path, .. } = lookup
2640            && path == requested
2641        {
2642            return self.workspace.head().await.map_err(workspace_error);
2643        }
2644        match self
2645            .promote(&requested, maximum_bytes, idempotency_key)
2646            .await?
2647        {
2648            TransactionCommit::Committed(generation)
2649            | TransactionCommit::AlreadyCommitted(generation) => Ok(generation),
2650            TransactionCommit::Conflict { .. } | TransactionCommit::Fenced => {
2651                Err(LazyWorkspaceError::StaleIdentity)
2652            }
2653            TransactionCommit::IdempotencyConflict => Err(LazyWorkspaceError::Concurrent),
2654        }
2655    }
2656
2657    /// Promotes one pinned source fact into an unpublished mount candidate.
2658    /// The caller owns the only eventual workspace publication; promotion
2659    /// itself must never advance HEAD ahead of dirty mounted mutations.
2660    #[cfg(feature = "native-mount")]
2661    pub(crate) async fn stage_exact_into_checkout(
2662        &self,
2663        checkout: &mut crate::Checkout<A, O>,
2664        path: &str,
2665        expected_source: FileId,
2666        maximum_bytes: u64,
2667        cancellation: &CancellationToken,
2668    ) -> Result<bool, LazyWorkspaceError> {
2669        let requested = self.canonical_path(path)?;
2670        let lookup = self
2671            .lookup_measured(&requested, WorkBudget::UNBOUNDED, cancellation)
2672            .await?
2673            .value;
2674        let actual = self.direct_file_id_for_lookup(&lookup.lookup);
2675        if actual != expected_source {
2676            return Err(LazyWorkspaceError::StaleIdentity);
2677        }
2678        let candidate_path = self.namespace_path(&requested)?;
2679        if let Some(record) = checkout
2680            .lookup_no_follow(&candidate_path, WorkBudget::UNBOUNDED, cancellation)
2681            .await
2682            .map_err(|failure| workspace_error(failure.error.into()))?
2683            .value
2684            .record
2685        {
2686            return (record.file_id == expected_source)
2687                .then_some(false)
2688                .ok_or(LazyWorkspaceError::StaleIdentity);
2689        }
2690        if matches!(
2691            &lookup.lookup,
2692            LazyLookup::Authored { path, .. } if path == &requested
2693        ) {
2694            return Err(LazyWorkspaceError::StaleIdentity);
2695        }
2696        let existing_identity = if matches!(
2697            &lookup.lookup,
2698            LazyLookup::Source(node) if node.kind != SourceNodeKind::Directory
2699        ) {
2700            match checkout
2701                .read_file_record_by_id(expected_source, WorkBudget::UNBOUNDED, cancellation)
2702                .await
2703            {
2704                Ok(receipt) => Some(receipt.value),
2705                Err(failure) if matches!(failure.error, crate::FsError::NotFound) => None,
2706                Err(failure) => {
2707                    return Err(LazyWorkspaceError::Workspace(failure.error.to_string()));
2708                }
2709            }
2710        } else {
2711            None
2712        };
2713        let mut transaction = crate::Transaction::for_checkout_candidate(
2714            &self.workspace,
2715            checkout.private_candidate(),
2716        );
2717        if let Some(parent) = parent_path(&requested) {
2718            transaction
2719                .create_dir_all_measured(parent, WorkBudget::UNBOUNDED, cancellation)
2720                .await
2721                .map_err(workspace_error)?;
2722        }
2723        if let Some(record) = existing_identity {
2724            transaction
2725                .restore_record_measured(&requested, record, WorkBudget::UNBOUNDED, cancellation)
2726                .await
2727                .map_err(workspace_error)?;
2728            *checkout = transaction.into_checkout_candidate();
2729            return Ok(true);
2730        }
2731        let changed = self
2732            .apply_resolved_promotion_measured(
2733                &mut transaction,
2734                &requested,
2735                lookup,
2736                maximum_bytes,
2737                WorkBudget::UNBOUNDED,
2738                cancellation,
2739            )
2740            .await?
2741            .value;
2742        if changed {
2743            *checkout = transaction.into_checkout_candidate();
2744        }
2745        Ok(changed)
2746    }
2747
2748    /// Returns one bounded page combining unresolved source entries with the
2749    /// authored overlay. Source-native order is followed by authored order;
2750    /// authored entries replace same-name source entries and tombstones hide
2751    /// source entries without forcing unrelated subtrees.
2752    pub async fn list_directory(
2753        &self,
2754        path: &str,
2755        cursor: Option<LazyDirectoryCursor>,
2756        maximum_entries: u32,
2757    ) -> Result<LazyDirectoryPage, LazyWorkspaceError> {
2758        let cancellation = CancellationToken::new();
2759        self.list_directory_measured(
2760            path,
2761            cursor,
2762            maximum_entries,
2763            WorkBudget::UNBOUNDED,
2764            &cancellation,
2765            None,
2766            None,
2767        )
2768        .await
2769        .map(|receipt| receipt.value)
2770    }
2771
2772    /// Reads the same merged sparse directory against an unpublished mounted
2773    /// checkout candidate instead of the workspace's last committed head.
2774    #[cfg(feature = "native-mount")]
2775    pub(crate) async fn list_directory_in_checkout(
2776        &self,
2777        checkout: &mut crate::Checkout<A, O>,
2778        path: &str,
2779        cursor: Option<LazyDirectoryCursor>,
2780        maximum_entries: u32,
2781        mounted_mask: Option<(&[String], bool)>,
2782    ) -> Result<LazyDirectoryPage, LazyWorkspaceError> {
2783        let cancellation = CancellationToken::new();
2784        self.list_directory_measured(
2785            path,
2786            cursor,
2787            maximum_entries,
2788            WorkBudget::UNBOUNDED,
2789            &cancellation,
2790            Some(checkout),
2791            mounted_mask,
2792        )
2793        .await
2794        .map(|receipt| receipt.value)
2795    }
2796
2797    /// A mounted replacement must not inherit entries from an unrelated
2798    /// source directory at the same path.
2799    #[allow(clippy::too_many_arguments)]
2800    async fn mounted_directory_replaces_source(
2801        &self,
2802        path: &str,
2803        directory: &NamespacePath,
2804        source: SourceReference,
2805        overlay: LazyOverlayId,
2806        checkout: &mut crate::Checkout<A, O>,
2807        work: &mut WorkCounters,
2808        budget: WorkBudget,
2809        cancellation: &CancellationToken,
2810    ) -> Result<bool, LazyWorkspaceError> {
2811        let candidate = checkout
2812            .lookup_no_follow(directory, remaining_work(*work, budget)?, cancellation)
2813            .await
2814            .map_err(|failure| LazyWorkspaceError::Workspace(failure.error.to_string()))?;
2815        *work = account_work(*work, candidate.work, budget)?;
2816        let tombstoned = self
2817            .tombstoned_measured(overlay, path, remaining_work(*work, budget)?, cancellation)
2818            .await?;
2819        *work = account_work(*work, tombstoned.work, budget)?;
2820        match candidate.value.record {
2821            None if tombstoned.value => Err(LazyWorkspaceError::NotFound),
2822            Some(_) if tombstoned.value => Ok(true),
2823            Some(record) => {
2824                let source = self
2825                    .source
2826                    .lookup(source, directory, cancellation)
2827                    .await
2828                    .map_err(|failure| failure.error)?;
2829                *work = account_work(*work, source.work, budget)?;
2830                Ok(source
2831                    .value
2832                    .is_none_or(|node| self.source_file_id(&node) != record.file_id))
2833            }
2834            None => Ok(false),
2835        }
2836    }
2837
2838    #[allow(
2839        clippy::too_many_lines,
2840        clippy::too_many_arguments,
2841        clippy::cognitive_complexity
2842    )]
2843    async fn list_directory_measured(
2844        &self,
2845        path: &str,
2846        cursor: Option<LazyDirectoryCursor>,
2847        maximum_entries: u32,
2848        budget: WorkBudget,
2849        cancellation: &CancellationToken,
2850        mut mounted: Option<&mut crate::Checkout<A, O>>,
2851        mounted_mask: Option<(&[String], bool)>,
2852    ) -> Result<OperationReceipt<LazyDirectoryPage>, LazyWorkspaceError> {
2853        if maximum_entries == 0 {
2854            return Err(LazyWorkspaceError::InvalidPageBound);
2855        }
2856        let state = self.state_measured(budget, cancellation).await?;
2857        let mut work = state.work;
2858        let state = state.value;
2859        let directory = self.namespace_path(path)?;
2860        let mut phase = match cursor {
2861            Some(cursor)
2862                if cursor.source == state.source
2863                    && cursor.overlay == state.overlay
2864                    && cursor.directory == directory =>
2865            {
2866                cursor.phase
2867            }
2868            Some(_) => return Err(LazyWorkspaceError::StaleCursor),
2869            None => LazyDirectoryPhase::Source(None),
2870        };
2871        if mounted_mask.is_some_and(|(_, opaque)| opaque)
2872            && matches!(phase, LazyDirectoryPhase::Source(None))
2873        {
2874            phase = LazyDirectoryPhase::Authored(None);
2875        }
2876        if matches!(phase, LazyDirectoryPhase::Source(None))
2877            && !directory.is_root()
2878            && let Some(checkout) = mounted.as_deref_mut()
2879            && self
2880                .mounted_directory_replaces_source(
2881                    path,
2882                    &directory,
2883                    state.source,
2884                    state.overlay,
2885                    checkout,
2886                    &mut work,
2887                    budget,
2888                    cancellation,
2889                )
2890                .await?
2891        {
2892            phase = LazyDirectoryPhase::Authored(None);
2893        }
2894        let mut source_absent = false;
2895        loop {
2896            match phase {
2897                LazyDirectoryPhase::Source(source_cursor) => {
2898                    let receipt = match self
2899                        .source
2900                        .list_page(
2901                            state.source,
2902                            &directory,
2903                            source_cursor,
2904                            maximum_entries,
2905                            cancellation,
2906                        )
2907                        .await
2908                    {
2909                        Ok(receipt) => receipt,
2910                        Err(failure)
2911                            if mounted.is_some()
2912                                && matches!(
2913                                    failure.error,
2914                                    DemandError::Absent | DemandError::NotDirectory
2915                                ) =>
2916                        {
2917                            work = account_work(work, *failure.work, budget)?;
2918                            source_absent = true;
2919                            phase = LazyDirectoryPhase::Authored(None);
2920                            continue;
2921                        }
2922                        Err(failure) => return Err(failure.error.into()),
2923                    };
2924                    work = account_work(work, receipt.work, budget)?;
2925                    let page = receipt.value;
2926                    if page.entries.len() > usize::try_from(maximum_entries).unwrap_or(usize::MAX) {
2927                        return Err(LazyWorkspaceError::Work(
2928                            "source directory page exceeded its requested bound".to_owned(),
2929                        ));
2930                    }
2931                    let temporary_bytes = retained_directory_page_bytes(
2932                        page.entries.capacity(),
2933                        std::mem::size_of::<SourceDirectoryEntry>(),
2934                        page.entries.iter().map(|entry| entry.name.retained_bytes()),
2935                    )?;
2936                    let mut entries = Vec::new();
2937                    let projection_bytes = reserve_lazy_directory_page(
2938                        &mut entries,
2939                        page.entries.len(),
2940                        temporary_bytes,
2941                        &mut work,
2942                        budget,
2943                    )?;
2944                    // A source page shares one authored generation. Resolve its
2945                    // candidate children together so the authenticated directory
2946                    // and file-table frontier is not rebuilt once per entry.
2947                    let count = page.entries.len();
2948                    let child_storage = count
2949                        .checked_mul(
2950                            std::mem::size_of::<NamespacePath>()
2951                                + std::mem::size_of::<(bool, Option<usize>)>()
2952                                + 1
2953                                + (directory.components().len() + 1)
2954                                    * std::mem::size_of::<LogicalName>(),
2955                        )
2956                        .and_then(|total| {
2957                            page.entries.iter().try_fold(total, |sum, entry| {
2958                                // Portable-to-native transcoding can expand a
2959                                // name (notably UTF-8 to Windows UTF-16).
2960                                sum.checked_add(entry.name.retained_bytes().saturating_mul(4))
2961                            })
2962                        })
2963                        .and_then(|total| {
2964                            total.checked_add(count.saturating_mul(path.len().saturating_mul(4)))
2965                        })
2966                        .and_then(|bytes| u64::try_from(bytes).ok())
2967                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
2968                    let live_bytes = projection_bytes
2969                        .checked_add(child_storage)
2970                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
2971                    work.peak_allocation_bytes = work.peak_allocation_bytes.max(live_bytes);
2972                    work.verify(budget)
2973                        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
2974                    let mut source_paths = Vec::new();
2975                    let mut slots = Vec::new();
2976                    source_paths.try_reserve_exact(count).map_err(|_| {
2977                        LazyWorkspaceError::Work("source page lookup allocation failed".to_owned())
2978                    })?;
2979                    slots.try_reserve_exact(count).map_err(|_| {
2980                        LazyWorkspaceError::Work("source page lookup allocation failed".to_owned())
2981                    })?;
2982                    let limits = self.workspace.limits();
2983                    for entry in &page.entries {
2984                        let child_path = logical_child_path(path, &entry.name);
2985                        if let Some(child) = child_path.as_ref() {
2986                            work = account_transient_string(work, child, live_bytes, budget)?;
2987                            if mounted_mask
2988                                .is_some_and(|(paths, _)| paths.binary_search(child).is_ok())
2989                            {
2990                                slots.push((true, None));
2991                                continue;
2992                            }
2993                            let tombstoned = self
2994                                .tombstoned_measured(
2995                                    state.overlay,
2996                                    child,
2997                                    remaining_work(work, budget)?,
2998                                    cancellation,
2999                                )
3000                                .await?;
3001                            work = account_nested_with_live_memory(
3002                                work,
3003                                tombstoned.work,
3004                                live_bytes,
3005                                budget,
3006                            )?;
3007                            if tombstoned.value {
3008                                slots.push((true, None));
3009                                continue;
3010                            }
3011                            let child = self.namespace_path(child)?;
3012                            slots.push((false, Some(source_paths.len())));
3013                            source_paths.push(child);
3014                        } else {
3015                            slots.push((false, None));
3016                        }
3017                    }
3018                    let mut authored = Vec::new();
3019                    authored
3020                        .try_reserve_exact(source_paths.len())
3021                        .map_err(|_| {
3022                            LazyWorkspaceError::Work(
3023                                "source page lookup allocation failed".to_owned(),
3024                            )
3025                        })?;
3026                    authored.resize(source_paths.len(), false);
3027                    if !source_paths.is_empty() {
3028                        let generation = if mounted.is_some() {
3029                            None
3030                        } else {
3031                            let head = self
3032                                .workspace
3033                                .head_measured(remaining_work(work, budget)?, cancellation)
3034                                .await
3035                                .map_err(workspace_error)?;
3036                            work = account_nested_with_live_memory(
3037                                work, head.work, live_bytes, budget,
3038                            )?;
3039                            Some(head.value)
3040                        };
3041                        let maximum = usize::try_from(limits.maximum_paths_per_batch)
3042                            .unwrap_or(usize::MAX)
3043                            .max(1);
3044                        let mut offset = 0_usize;
3045                        for chunk in source_paths.chunks(maximum) {
3046                            let (records, lookup_work) =
3047                                if let Some(checkout) = mounted.as_deref_mut() {
3048                                    let lookup = checkout
3049                                        .lookup_batch_no_follow(
3050                                            chunk,
3051                                            remaining_work(work, budget)?,
3052                                            cancellation,
3053                                        )
3054                                        .await
3055                                        .map_err(|failure| {
3056                                            LazyWorkspaceError::Workspace(failure.error.to_string())
3057                                        })?;
3058                                    (
3059                                        lookup
3060                                            .value
3061                                            .entries
3062                                            .into_iter()
3063                                            .map(|entry| entry.record)
3064                                            .collect::<Vec<_>>(),
3065                                        lookup.work,
3066                                    )
3067                                } else {
3068                                    let lookup = generation
3069                                        .as_ref()
3070                                        .ok_or(LazyWorkspaceError::Concurrent)?
3071                                        .lookup_paths(
3072                                            chunk,
3073                                            remaining_work(work, budget)?,
3074                                            cancellation,
3075                                        )
3076                                        .await
3077                                        .map_err(|failure| {
3078                                            LazyWorkspaceError::Workspace(failure.error.to_string())
3079                                        })?;
3080                                    (lookup.value, lookup.work)
3081                                };
3082                            work = account_nested_with_live_memory(
3083                                work,
3084                                lookup_work,
3085                                live_bytes,
3086                                budget,
3087                            )?;
3088                            for (slot, record) in authored
3089                                .get_mut(offset..offset + chunk.len())
3090                                .ok_or_else(|| {
3091                                    LazyWorkspaceError::Work(
3092                                        "lookup result offset overflow".to_owned(),
3093                                    )
3094                                })?
3095                                .iter_mut()
3096                                .zip(records)
3097                            {
3098                                *slot = record.is_some();
3099                            }
3100                            offset += chunk.len();
3101                        }
3102                    }
3103                    for (entry, (hidden, index)) in page.entries.into_iter().zip(slots) {
3104                        if hidden
3105                            || index
3106                                .is_some_and(|index| authored.get(index).copied().unwrap_or(false))
3107                        {
3108                            continue;
3109                        }
3110                        entries.push(LazyDirectoryEntry {
3111                            name: entry.name,
3112                            kind: entry.kind,
3113                            authored: false,
3114                        });
3115                    }
3116                    let next = Some(LazyDirectoryCursor {
3117                        source: state.source,
3118                        overlay: state.overlay,
3119                        directory,
3120                        phase: page
3121                            .next
3122                            .map_or(LazyDirectoryPhase::Authored(None), |next| {
3123                                LazyDirectoryPhase::Source(Some(next))
3124                            }),
3125                    });
3126                    return Ok(OperationReceipt {
3127                        value: LazyDirectoryPage { entries, next },
3128                        work,
3129                    });
3130                }
3131                LazyDirectoryPhase::Authored(after) => {
3132                    let authored_page = if let Some(checkout) = mounted {
3133                        match checkout
3134                            .list_directory(
3135                                &directory,
3136                                after.as_ref(),
3137                                maximum_entries,
3138                                remaining_work(work, budget)?,
3139                                cancellation,
3140                            )
3141                            .await
3142                        {
3143                            Ok(page) => Ok(OperationReceipt {
3144                                value: crate::WorkspaceDirectoryPage {
3145                                    entries: page
3146                                        .value
3147                                        .entries
3148                                        .into_iter()
3149                                        .map(|entry| crate::WorkspaceDirectoryEntry {
3150                                            name: entry.name,
3151                                            file_id: entry.file_id,
3152                                            kind: entry.kind,
3153                                        })
3154                                        .collect(),
3155                                    has_more: page.value.has_more,
3156                                },
3157                                work: page.work,
3158                            }),
3159                            Err(failure) if matches!(failure.error, crate::FsError::NotFound) => {
3160                                Err(WorkspaceError::NotFound)
3161                            }
3162                            Err(failure) => Err(WorkspaceError::from(failure.error)),
3163                        }
3164                    } else {
3165                        self.workspace
3166                            .list_directory_measured(
3167                                path,
3168                                after.as_ref(),
3169                                maximum_entries,
3170                                remaining_work(work, budget)?,
3171                                cancellation,
3172                            )
3173                            .await
3174                    };
3175                    let page = match authored_page {
3176                        Ok(page) => page,
3177                        Err(WorkspaceError::NotFound) if source_absent => {
3178                            return Err(LazyWorkspaceError::NotFound);
3179                        }
3180                        Err(WorkspaceError::NotFound) => {
3181                            return Ok(OperationReceipt {
3182                                value: LazyDirectoryPage {
3183                                    entries: Vec::new(),
3184                                    next: None,
3185                                },
3186                                work,
3187                            });
3188                        }
3189                        Err(error) => return Err(workspace_error(error)),
3190                    };
3191                    work = account_work(work, page.work, budget)?;
3192                    let page = page.value;
3193                    if page.entries.len() > usize::try_from(maximum_entries).unwrap_or(usize::MAX) {
3194                        return Err(LazyWorkspaceError::Work(
3195                            "authored directory page exceeded its requested bound".to_owned(),
3196                        ));
3197                    }
3198                    let next = if page.has_more {
3199                        page.entries.last().map(|entry| LazyDirectoryCursor {
3200                            source: state.source,
3201                            overlay: state.overlay,
3202                            directory,
3203                            phase: LazyDirectoryPhase::Authored(Some(entry.name.clone())),
3204                        })
3205                    } else {
3206                        None
3207                    };
3208                    let temporary_bytes = retained_directory_page_bytes(
3209                        page.entries.capacity(),
3210                        std::mem::size_of::<crate::WorkspaceDirectoryEntry>(),
3211                        page.entries.iter().map(|entry| entry.name.retained_bytes()),
3212                    )?;
3213                    let mut entries = Vec::new();
3214                    let _projection_bytes = reserve_lazy_directory_page(
3215                        &mut entries,
3216                        page.entries.len(),
3217                        temporary_bytes,
3218                        &mut work,
3219                        budget,
3220                    )?;
3221                    entries.extend(page.entries.into_iter().map(authored_entry));
3222                    return Ok(OperationReceipt {
3223                        value: LazyDirectoryPage { entries, next },
3224                        work,
3225                    });
3226                }
3227            }
3228        }
3229    }
3230
3231    /// Captures every currently visible path into an exact authored generation.
3232    ///
3233    /// Enumeration completes before promotion begins so authored overlay
3234    /// changes cannot invalidate source cursors. Budget exhaustion and
3235    /// cancellation are explicit errors; callers must never present a partial
3236    /// capture as exact. A retry safely continues from already promoted paths.
3237    pub async fn exactify(
3238        &self,
3239        budget: WorkBudget,
3240        cancellation: &CancellationToken,
3241    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
3242        self.exactify_with_permit(budget, cancellation, crate::PublicationPermit::Unrestricted)
3243            .await
3244    }
3245
3246    /// Exactifies under the supplied operation-window or reservation permit.
3247    #[allow(clippy::too_many_lines)]
3248    pub async fn exactify_with_permit(
3249        &self,
3250        budget: WorkBudget,
3251        cancellation: &CancellationToken,
3252        permit: crate::PublicationPermit,
3253    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
3254        self.exactify_selected_with_permit("/", budget, cancellation, permit)
3255            .await
3256    }
3257
3258    /// Captures one visible subtree into authored state without visiting its
3259    /// siblings. The selected directory and every descendant become exact;
3260    /// unresolved paths outside it retain their normal lazy semantics.
3261    pub async fn exactify_subtree(
3262        &self,
3263        path: &str,
3264        budget: WorkBudget,
3265        cancellation: &CancellationToken,
3266    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
3267        self.exactify_subtree_with_permit(
3268            path,
3269            budget,
3270            cancellation,
3271            crate::PublicationPermit::Unrestricted,
3272        )
3273        .await
3274    }
3275
3276    /// Captures one visible subtree into authored state without visiting its
3277    /// siblings. The selected directory and every descendant become exact;
3278    /// unresolved paths outside it retain their normal lazy semantics.
3279    pub async fn exactify_subtree_with_permit(
3280        &self,
3281        path: &str,
3282        budget: WorkBudget,
3283        cancellation: &CancellationToken,
3284        permit: crate::PublicationPermit,
3285    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
3286        let root = self.canonical_path(path)?;
3287        let selected = self.lookup(&root).await?;
3288        if !matches!(
3289            selected,
3290            LazyLookup::Authored {
3291                stat: crate::WorkspaceStat {
3292                    kind: FileKind::Directory,
3293                    ..
3294                },
3295                ..
3296            } | LazyLookup::Source(SourceNode {
3297                kind: SourceNodeKind::Directory,
3298                ..
3299            }) | LazyLookup::Shadow {
3300                record: FileRecord {
3301                    kind: FileKind::Directory,
3302                    ..
3303                },
3304                ..
3305            }
3306        ) {
3307            return Err(LazyWorkspaceError::NotDirectory);
3308        }
3309        self.exactify_selected_with_permit(&root, budget, cancellation, permit)
3310            .await
3311    }
3312
3313    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
3314    async fn exactify_selected_with_permit(
3315        &self,
3316        root: &str,
3317        budget: WorkBudget,
3318        cancellation: &CancellationToken,
3319        permit: crate::PublicationPermit,
3320    ) -> Result<OperationReceipt<crate::Generation<A, O>>, LazyWorkspaceError> {
3321        cancellation
3322            .check()
3323            .map_err(|_| LazyWorkspaceError::Cancelled)?;
3324        let state = self.state_measured(budget, cancellation).await?;
3325        let mut work = state.work;
3326        let head = self
3327            .workspace
3328            .head_measured(remaining_work(work, budget)?, cancellation)
3329            .await
3330            .map_err(workspace_error)?;
3331        work = account_work(work, head.work, budget)?;
3332        let mut pinned_generation = head.value;
3333        let capture = LazySnapshotRef::new(
3334            self.workspace.id(),
3335            pinned_generation.id(),
3336            state.value.source,
3337            state.value.overlay,
3338            state.value.shadows,
3339        )
3340        .id;
3341        let mut retained_bytes = 0_u64;
3342        let mut directories = Vec::new();
3343        let mut paths = Vec::new();
3344        reserve_exactify_path_slot(&mut directories, &mut retained_bytes, &mut work, budget)?;
3345        let root_key = root;
3346        let root = root.to_owned();
3347        retain_exactify_string(&root, &mut retained_bytes, &mut work, budget)?;
3348        directories.push(root.clone());
3349        if root != "/" {
3350            reserve_exactify_path_slot(&mut paths, &mut retained_bytes, &mut work, budget)?;
3351            retain_exactify_string(&root, &mut retained_bytes, &mut work, budget)?;
3352            paths.push(root);
3353        }
3354        let mut next_directory = 0_usize;
3355        while next_directory < directories.len() {
3356            cancellation
3357                .check()
3358                .map_err(|_| LazyWorkspaceError::Cancelled)?;
3359            let directory = directories
3360                .get_mut(next_directory)
3361                .map(std::mem::take)
3362                .ok_or(LazyWorkspaceError::Concurrent)?;
3363            next_directory += 1;
3364            let mut cursor = None;
3365            loop {
3366                cancellation
3367                    .check()
3368                    .map_err(|_| LazyWorkspaceError::Cancelled)?;
3369                let receipt = self
3370                    .list_directory_measured(
3371                        &directory,
3372                        cursor,
3373                        1_024,
3374                        remaining_work(work, budget)?,
3375                        cancellation,
3376                        None,
3377                        None,
3378                    )
3379                    .await
3380                    .map_err(|error| exactify_error("enumerate", &directory, error))?;
3381                work = account_nested_with_live_memory(work, receipt.work, retained_bytes, budget)?;
3382                let page = receipt.value;
3383                for entry in page.entries {
3384                    let path = logical_child_path(&directory, &entry.name)
3385                        .ok_or(LazyWorkspaceError::UnsupportedNode)?;
3386                    work.items_examined = work
3387                        .items_examined
3388                        .checked_add(1)
3389                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3390                    work.verify(budget)
3391                        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
3392                    if entry.kind == SourceNodeKind::Directory {
3393                        reserve_exactify_path_slot(
3394                            &mut directories,
3395                            &mut retained_bytes,
3396                            &mut work,
3397                            budget,
3398                        )?;
3399                        let child_directory = path.clone();
3400                        retain_exactify_string(
3401                            &child_directory,
3402                            &mut retained_bytes,
3403                            &mut work,
3404                            budget,
3405                        )?;
3406                        directories.push(child_directory);
3407                    }
3408                    reserve_exactify_path_slot(&mut paths, &mut retained_bytes, &mut work, budget)?;
3409                    retain_exactify_string(&path, &mut retained_bytes, &mut work, budget)?;
3410                    paths.push(path);
3411                }
3412                cursor = page.next;
3413                if cursor.is_none() {
3414                    break;
3415                }
3416            }
3417            retained_bytes = retained_bytes
3418                .checked_sub(u64::try_from(directory.capacity()).unwrap_or(u64::MAX))
3419                .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3420        }
3421        let directory_vector_bytes = exactify_path_vector_bytes(&directories)?;
3422        retained_bytes = retained_bytes
3423            .checked_sub(directory_vector_bytes)
3424            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3425        drop(directories);
3426        let path_vector_bytes = exactify_path_vector_bytes(&paths)?;
3427        paths.sort_unstable_by(|left, right| exactify_path_order(left, right));
3428        let limits = self.workspace.limits();
3429        // A path can add an ancestor proof for every component. Keep each
3430        // unpublished candidate well inside the volume dependency ceiling.
3431        let dependency_paths = usize::try_from(limits.maximum_checkout_dependencies)
3432            .unwrap_or(usize::MAX)
3433            / (usize::from(limits.maximum_path_depth) + 4);
3434        let mutation_paths =
3435            usize::try_from(limits.maximum_mutations_per_batch).unwrap_or(usize::MAX) / 4;
3436        let maximum_batch_paths = dependency_paths.min(mutation_paths).max(1);
3437        let mut seen_sources: Vec<(FileId, String)> = Vec::new();
3438        seen_sources
3439            .try_reserve_exact(maximum_batch_paths.min(paths.len()))
3440            .map_err(|_| LazyWorkspaceError::Work("exactify batch allocation failed".to_owned()))?;
3441        let seen_vector_bytes = u64::try_from(
3442            seen_sources
3443                .capacity()
3444                .checked_mul(std::mem::size_of::<(FileId, String)>())
3445                .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
3446        )
3447        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3448        retained_bytes = retained_bytes
3449            .checked_add(seen_vector_bytes)
3450            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3451        work.peak_allocation_bytes = work.peak_allocation_bytes.max(retained_bytes);
3452        work.verify(budget)
3453            .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
3454        let mut transaction = None;
3455        let mut batch_paths = 0_usize;
3456        let mut batch_dirty = false;
3457        let mut batch_index = 0_u64;
3458        let mut pending_sources = Vec::new();
3459        let mut pending_source_bytes = 0_u64;
3460        let mut authored_batch = Vec::new();
3461        for index in 0..paths.len() {
3462            cancellation
3463                .check()
3464                .map_err(|_| LazyWorkspaceError::Cancelled)?;
3465            if batch_paths == maximum_batch_paths {
3466                if batch_dirty {
3467                    work = self
3468                        .flush_exactify_source_batch(
3469                            transaction.as_mut().ok_or(LazyWorkspaceError::Concurrent)?,
3470                            &mut pending_sources,
3471                            &mut pending_source_bytes,
3472                            &mut retained_bytes,
3473                            work,
3474                            budget,
3475                            cancellation,
3476                        )
3477                        .await?;
3478                    let current = transaction.take().ok_or(LazyWorkspaceError::Concurrent)?;
3479                    let committed = self
3480                        .commit_exactify_batch(
3481                            current,
3482                            permit,
3483                            remaining_work(work, budget)?,
3484                            cancellation,
3485                        )
3486                        .await?;
3487                    work = account_nested_with_live_memory(
3488                        work,
3489                        committed.work,
3490                        retained_bytes,
3491                        budget,
3492                    )?;
3493                    pinned_generation = committed.value;
3494                }
3495                transaction = None;
3496                batch_dirty = false;
3497                batch_paths = 0;
3498                batch_index = batch_index
3499                    .checked_add(1)
3500                    .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3501                for (_, retained) in seen_sources.drain(..) {
3502                    retained_bytes = retained_bytes
3503                        .checked_sub(u64::try_from(retained.capacity()).unwrap_or(u64::MAX))
3504                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3505                }
3506            }
3507            if batch_paths == 0 {
3508                let end = index.saturating_add(maximum_batch_paths).min(paths.len());
3509                let namespace_paths = paths
3510                    .get(index..end)
3511                    .ok_or(LazyWorkspaceError::Concurrent)?
3512                    .iter()
3513                    .map(|path| self.namespace_path(path))
3514                    .collect::<Result<Vec<_>, _>>()?;
3515                let found = pinned_generation
3516                    .lookup_paths(
3517                        &namespace_paths,
3518                        remaining_work(work, budget)?,
3519                        cancellation,
3520                    )
3521                    .await
3522                    .map_err(|failure| LazyWorkspaceError::Workspace(failure.error.to_string()))?;
3523                work = account_nested_with_live_memory(work, found.work, retained_bytes, budget)?;
3524                authored_batch = found
3525                    .value
3526                    .into_iter()
3527                    .map(|record| record.is_some())
3528                    .collect();
3529            }
3530            let authored_absent = !authored_batch
3531                .get(batch_paths)
3532                .copied()
3533                .ok_or(LazyWorkspaceError::Concurrent)?;
3534            let path = std::mem::take(paths.get_mut(index).ok_or(LazyWorkspaceError::Concurrent)?);
3535            let receipt = if authored_absent {
3536                self.inspect_snapshot_after_authored_absence(
3537                    &path,
3538                    remaining_work(work, budget)?,
3539                    cancellation,
3540                )
3541                .await
3542            } else {
3543                self.inspect_snapshot_measured(&path, remaining_work(work, budget)?, cancellation)
3544                    .await
3545            }
3546            .map_err(|error| exactify_error("inspect", &path, error))?;
3547            work = account_nested_with_live_memory(work, receipt.work, retained_bytes, budget)?;
3548            let lookup = receipt.value;
3549            if matches!(lookup.lookup, LazyLookup::Source(_)) {
3550                work.materializations = work
3551                    .materializations
3552                    .checked_add(1)
3553                    .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3554                work.verify(budget)
3555                    .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
3556            }
3557            if transaction.is_none() {
3558                let begun = self
3559                    .workspace
3560                    .begin_pinned_transaction_measured(
3561                        &pinned_generation,
3562                        exactify_batch_key(capture, root_key, batch_index),
3563                        remaining_work(work, budget)?,
3564                        cancellation,
3565                    )
3566                    .await
3567                    .map_err(workspace_error)?;
3568                work = account_nested_with_live_memory(work, begun.work, retained_bytes, budget)?;
3569                transaction = Some(begun.value);
3570                if let Some(parent) = parent_path(root_key) {
3571                    let prepared = transaction
3572                        .as_mut()
3573                        .ok_or(LazyWorkspaceError::Concurrent)?
3574                        .create_dir_all_measured(
3575                            parent,
3576                            remaining_work(work, budget)?,
3577                            cancellation,
3578                        )
3579                        .await
3580                        .map_err(workspace_error)?;
3581                    work = account_nested_with_live_memory(work, prepared, retained_bytes, budget)?;
3582                }
3583            }
3584            let source_id = match &lookup.lookup {
3585                LazyLookup::Source(node)
3586                    if node.kind == SourceNodeKind::RegularFile && node.link_count != Some(1) =>
3587                {
3588                    Some(self.source_file_id(node))
3589                }
3590                _ => None,
3591            };
3592            if let Some((_, alias)) =
3593                source_id.and_then(|id| seen_sources.iter().find(|(seen, _)| *seen == id))
3594            {
3595                work = self
3596                    .flush_exactify_source_batch(
3597                        transaction.as_mut().ok_or(LazyWorkspaceError::Concurrent)?,
3598                        &mut pending_sources,
3599                        &mut pending_source_bytes,
3600                        &mut retained_bytes,
3601                        work,
3602                        budget,
3603                        cancellation,
3604                    )
3605                    .await?;
3606                let linked = transaction
3607                    .as_mut()
3608                    .ok_or(LazyWorkspaceError::Concurrent)?
3609                    .hard_link_measured(alias, &path, remaining_work(work, budget)?, cancellation)
3610                    .await
3611                    .map_err(workspace_error)
3612                    .map_err(|error| exactify_error("link", &path, error))?;
3613                work = account_nested_with_live_memory(work, linked, retained_bytes, budget)?;
3614                batch_dirty = true;
3615                retained_bytes = retained_bytes
3616                    .checked_sub(u64::try_from(path.capacity()).unwrap_or(u64::MAX))
3617                    .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3618            } else if let LazyLookup::Source(node) = &lookup.lookup
3619                && node.kind == SourceNodeKind::RegularFile
3620            {
3621                let pinned = PinnedSourceNode {
3622                    source: lookup.source.ok_or(LazyWorkspaceError::Concurrent)?,
3623                    node: *node,
3624                };
3625                let staged = self
3626                    .stage_regular_source_content(
3627                        transaction.as_ref().ok_or(LazyWorkspaceError::Concurrent)?,
3628                        &path,
3629                        &pinned,
3630                        remaining_work(work, budget)?,
3631                        cancellation,
3632                    )
3633                    .await
3634                    .map_err(|error| exactify_error("stage", &path, error))?;
3635                work = account_nested_with_live_memory(work, staged.work, retained_bytes, budget)?;
3636                let namespace_path = self.namespace_path(&path)?;
3637                queue_exactify_mutation(
3638                    &mut pending_sources,
3639                    AuthoredMutation::CreateFileFromContent {
3640                        path: namespace_path,
3641                        content: staged.value,
3642                        metadata: source_file_metadata(node.metadata),
3643                        file_id: Some(self.source_file_id(node)),
3644                    },
3645                    &mut pending_source_bytes,
3646                    &mut retained_bytes,
3647                    &mut work,
3648                    budget,
3649                )?;
3650                batch_dirty = true;
3651                if let Some(id) = source_id {
3652                    seen_sources.push((id, path));
3653                } else {
3654                    retained_bytes = retained_bytes
3655                        .checked_sub(u64::try_from(path.capacity()).unwrap_or(u64::MAX))
3656                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3657                }
3658            } else if let LazyLookup::Source(node) = &lookup.lookup
3659                && node.kind == SourceNodeKind::Directory
3660            {
3661                queue_exactify_mutation(
3662                    &mut pending_sources,
3663                    AuthoredMutation::CreateDirectory {
3664                        path: self.namespace_path(&path)?,
3665                        metadata: source_file_metadata(node.metadata),
3666                    },
3667                    &mut pending_source_bytes,
3668                    &mut retained_bytes,
3669                    &mut work,
3670                    budget,
3671                )?;
3672                batch_dirty = true;
3673                retained_bytes = retained_bytes
3674                    .checked_sub(u64::try_from(path.capacity()).unwrap_or(u64::MAX))
3675                    .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3676            } else {
3677                work = self
3678                    .flush_exactify_source_batch(
3679                        transaction.as_mut().ok_or(LazyWorkspaceError::Concurrent)?,
3680                        &mut pending_sources,
3681                        &mut pending_source_bytes,
3682                        &mut retained_bytes,
3683                        work,
3684                        budget,
3685                        cancellation,
3686                    )
3687                    .await?;
3688                let applied = self
3689                    .apply_resolved_promotion_measured(
3690                        transaction.as_mut().ok_or(LazyWorkspaceError::Concurrent)?,
3691                        &path,
3692                        lookup,
3693                        budget.source_bytes_read,
3694                        remaining_work(work, budget)?,
3695                        cancellation,
3696                    )
3697                    .await
3698                    .map_err(|error| exactify_error("promote", &path, error))?;
3699                work = account_nested_with_live_memory(work, applied.work, retained_bytes, budget)?;
3700                batch_dirty |= applied.value;
3701                if let Some(id) = source_id.filter(|_| applied.value) {
3702                    seen_sources.push((id, path));
3703                } else {
3704                    retained_bytes = retained_bytes
3705                        .checked_sub(u64::try_from(path.capacity()).unwrap_or(u64::MAX))
3706                        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3707                }
3708            }
3709            batch_paths += 1;
3710        }
3711        if batch_dirty {
3712            work = self
3713                .flush_exactify_source_batch(
3714                    transaction.as_mut().ok_or(LazyWorkspaceError::Concurrent)?,
3715                    &mut pending_sources,
3716                    &mut pending_source_bytes,
3717                    &mut retained_bytes,
3718                    work,
3719                    budget,
3720                    cancellation,
3721                )
3722                .await?;
3723            let current = transaction.take().ok_or(LazyWorkspaceError::Concurrent)?;
3724            let committed = self
3725                .commit_exactify_batch(current, permit, remaining_work(work, budget)?, cancellation)
3726                .await?;
3727            work = account_nested_with_live_memory(work, committed.work, retained_bytes, budget)?;
3728            pinned_generation = committed.value;
3729        }
3730        for (_, retained) in seen_sources.drain(..) {
3731            retained_bytes = retained_bytes
3732                .checked_sub(u64::try_from(retained.capacity()).unwrap_or(u64::MAX))
3733                .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3734        }
3735        retained_bytes = retained_bytes
3736            .checked_sub(seen_vector_bytes)
3737            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3738        drop(seen_sources);
3739        retained_bytes = retained_bytes
3740            .checked_sub(path_vector_bytes)
3741            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
3742        debug_assert_eq!(retained_bytes, 0);
3743        let head = self
3744            .workspace
3745            .head_measured(remaining_work(work, budget)?, cancellation)
3746            .await
3747            .map_err(workspace_error)
3748            .map_err(|error| exactify_error("load head", "/", error))?;
3749        work = account_work(work, head.work, budget)?;
3750        if head.value.id() != pinned_generation.id() {
3751            return Err(LazyWorkspaceError::Concurrent);
3752        }
3753        Ok(OperationReceipt {
3754            value: head.value,
3755            work,
3756        })
3757    }
3758
3759    /// Creates or replaces one complete authored file.
3760    ///
3761    /// Authored state always wins over the source overlay, so no companion
3762    /// metadata mutation is needed. An inherited tombstone deliberately stays
3763    /// attached to the underlying source fact: deleting the authored
3764    /// replacement later must not resurrect the source file.
3765    pub async fn write(
3766        &self,
3767        path: &str,
3768        bytes: Bytes,
3769    ) -> Result<TransactionCommit<A, O>, LazyWorkspaceError> {
3770        let path = self.canonical_path(path)?;
3771        let source_identity = match self.lookup(&path).await {
3772            Ok(LazyLookup::Source(node)) => Some(self.source_file_id(&node)),
3773            Ok(LazyLookup::Authored { stat, .. }) => {
3774                self.promote_exact(&path, stat.file_id, u64::MAX, IdempotencyKey::new())
3775                    .await?;
3776                None
3777            }
3778            Ok(LazyLookup::Shadow { record, .. }) => {
3779                self.promote_exact(&path, record.file_id, u64::MAX, IdempotencyKey::new())
3780                    .await?;
3781                None
3782            }
3783            Err(LazyWorkspaceError::NotFound) => None,
3784            Err(error) => return Err(error),
3785        };
3786        let mut transaction = self
3787            .workspace
3788            .begin_transaction(IdempotencyKey::new())
3789            .await
3790            .map_err(workspace_error)?;
3791        if let Some(parent) = parent_path(&path) {
3792            transaction
3793                .create_dir_all(parent)
3794                .await
3795                .map_err(workspace_error)?;
3796        }
3797        transaction
3798            .write(&path, bytes)
3799            .await
3800            .map_err(workspace_error)?;
3801        if let Some(file_id) = source_identity {
3802            transaction
3803                .preserve_file_identity(&path, file_id)
3804                .await
3805                .map_err(workspace_error)?;
3806        }
3807        transaction.commit().await.map_err(workspace_error)
3808    }
3809
3810    /// Prepares mounted whiteouts and identity updates under the checkout's
3811    /// exact publication identity. The caller must hold the mount mutation gate
3812    /// through checkout seal and then call `recover_pending_remove`.
3813    #[cfg(feature = "native-mount")]
3814    pub(crate) async fn prepare_mount_removals(
3815        &self,
3816        paths: &[String],
3817        identity_records: &[(FileRecord, FileMetadata)],
3818        idempotency_key: IdempotencyKey,
3819    ) -> Result<(), LazyWorkspaceError> {
3820        if paths.is_empty() && identity_records.is_empty() {
3821            return Ok(());
3822        }
3823        for _ in 0..MAXIMUM_STATE_RETRIES {
3824            let state = self.state().await?;
3825            let mut overlay = state.overlay;
3826            let mut shadows = state.shadows;
3827            for path in paths {
3828                let path = self.canonical_path(path)?;
3829                overlay = self
3830                    .insert_overlay(overlay, path, LazyOverlayChange::Tombstone)
3831                    .await?;
3832            }
3833            for (record, metadata) in identity_records {
3834                shadows = self
3835                    .insert_shadow(
3836                        shadows,
3837                        record.file_id,
3838                        *record,
3839                        crate::WorkspaceMetadata::from_engine(*metadata),
3840                    )
3841                    .await?;
3842            }
3843            let prepared = LazyWorkspaceState {
3844                revision: state.revision.saturating_add(1),
3845                overlay,
3846                shadows,
3847                pending_remove: Some(PendingLazyRemove {
3848                    prior_overlay: state.overlay,
3849                    prior_shadows: state.shadows,
3850                    prepared_overlay: overlay,
3851                    kind: PendingLazyRemoveKind::Mounted { idempotency_key },
3852                }),
3853                ..state.clone()
3854            };
3855            if self
3856                .store
3857                .compare_and_swap_lazy_workspace(self.workspace.id(), state.revision, prepared)
3858                .await
3859                .map_err(store_error)?
3860            {
3861                return Ok(());
3862            }
3863        }
3864        Err(LazyWorkspaceError::Concurrent)
3865    }
3866
3867    /// Reads the durable decision for a prepared mounted overlay without
3868    /// finalizing it. An ambiguously acknowledged checkout must first retry
3869    /// its exact operation ID so its private candidate adopts the commit.
3870    #[cfg(feature = "native-mount")]
3871    pub(crate) async fn mount_removal_publication(
3872        &self,
3873    ) -> Result<Option<bool>, LazyWorkspaceError> {
3874        let state = self
3875            .store
3876            .load_lazy_workspace(self.workspace.id())
3877            .await
3878            .map_err(store_error)?
3879            .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
3880        let Some(PendingLazyRemove {
3881            kind: PendingLazyRemoveKind::Mounted { idempotency_key },
3882            ..
3883        }) = state.pending_remove
3884        else {
3885            return Ok(None);
3886        };
3887        let committed = self
3888            .workspace
3889            .operation_generation(idempotency_key)
3890            .await
3891            .map_err(workspace_error)?
3892            .is_some();
3893        Ok(Some(committed))
3894    }
3895
3896    /// Resolves a prepared mounted overlay from the checkout's durable result.
3897    #[cfg(feature = "native-mount")]
3898    pub(crate) async fn finish_mount_removals(&self) -> Result<bool, LazyWorkspaceError> {
3899        let Some(committed) = self.mount_removal_publication().await? else {
3900            return Ok(false);
3901        };
3902        self.recover_pending_remove().await?;
3903        Ok(committed)
3904    }
3905
3906    /// Removes one path without confusing authored deletion with unresolved source state.
3907    pub async fn remove(&self, path: &str) -> Result<(), LazyWorkspaceError> {
3908        self.remove_if(path, None).await
3909    }
3910
3911    /// Removes one path only if it still has the caller's resolved identity.
3912    #[allow(clippy::too_many_lines)]
3913    pub async fn remove_if(
3914        &self,
3915        path: &str,
3916        expected: Option<FileId>,
3917    ) -> Result<(), LazyWorkspaceError> {
3918        let path = self.canonical_path(path)?;
3919        let mut resolved = self.lookup(&path).await?;
3920        if let LazyLookup::Authored {
3921            path: authored_path,
3922            stat,
3923        } = &resolved
3924            && authored_path != &path
3925        {
3926            self.promote_exact(&path, stat.file_id, u64::MAX, IdempotencyKey::new())
3927                .await?;
3928            resolved = self.lookup(&path).await?;
3929        }
3930        let actual = self.direct_file_id_for_lookup(&resolved);
3931        if let Some(expected) = expected
3932            && actual != expected
3933        {
3934            return Err(LazyWorkspaceError::StaleIdentity);
3935        }
3936        let removal_kind = if matches!(resolved, LazyLookup::Authored { .. }) {
3937            PendingLazyRemoveKind::Authored {
3938                idempotency_key: IdempotencyKey::new(),
3939            }
3940        } else {
3941            PendingLazyRemoveKind::SourceOnly
3942        };
3943        let mut prepared = None;
3944        for _ in 0..MAXIMUM_STATE_RETRIES {
3945            let state = self.state().await?;
3946            let shadows = if let LazyLookup::Authored { stat, .. } = &resolved {
3947                let record = self
3948                    .workspace
3949                    .record_by_id(stat.file_id)
3950                    .await
3951                    .map_err(workspace_error)?;
3952                self.insert_shadow(state.shadows, stat.file_id, record, stat.metadata)
3953                    .await?
3954            } else {
3955                state.shadows
3956            };
3957            let tombstone_overlay = self
3958                .insert_overlay(state.overlay, path.clone(), LazyOverlayChange::Tombstone)
3959                .await?;
3960            let pending_remove = PendingLazyRemove {
3961                prior_overlay: state.overlay,
3962                prior_shadows: state.shadows,
3963                prepared_overlay: tombstone_overlay,
3964                kind: removal_kind,
3965            };
3966            let replacement = LazyWorkspaceState {
3967                revision: state.revision.saturating_add(1),
3968                overlay: tombstone_overlay,
3969                shadows,
3970                pending_remove: Some(pending_remove),
3971                ..state.clone()
3972            };
3973            if self
3974                .store
3975                .compare_and_swap_lazy_workspace(
3976                    self.workspace.id(),
3977                    state.revision,
3978                    replacement.clone(),
3979                )
3980                .await
3981                .map_err(store_error)?
3982            {
3983                prepared = Some(replacement);
3984                break;
3985            }
3986        }
3987        let prepared = prepared.ok_or(LazyWorkspaceError::Concurrent)?;
3988
3989        let removal = if let PendingLazyRemoveKind::Authored { idempotency_key } = removal_kind {
3990            let mut transaction = self
3991                .workspace
3992                .begin_transaction(idempotency_key)
3993                .await
3994                .map_err(workspace_error)?;
3995            match transaction.remove_if(&path, actual).await {
3996                Ok(()) => transaction.commit().await,
3997                Err(error) => Err(error),
3998            }
3999        } else {
4000            Ok(TransactionCommit::AlreadyCommitted(
4001                self.workspace.head().await.map_err(workspace_error)?,
4002            ))
4003        };
4004        let prior_overlay = prepared
4005            .pending_remove
4006            .as_ref()
4007            .ok_or(LazyWorkspaceError::Concurrent)?
4008            .prior_overlay;
4009        let (overlay, result) = match removal {
4010            Ok(TransactionCommit::Committed(_))
4011            | Ok(TransactionCommit::AlreadyCommitted(_))
4012            | Err(WorkspaceError::NotFound) => (prepared.overlay, Ok(())),
4013            Ok(TransactionCommit::Conflict { .. }) | Ok(TransactionCommit::Fenced) => {
4014                (prior_overlay, Err(LazyWorkspaceError::StaleIdentity))
4015            }
4016            Ok(TransactionCommit::IdempotencyConflict) => {
4017                (prior_overlay, Err(LazyWorkspaceError::Concurrent))
4018            }
4019            Err(error) => (prior_overlay, Err(workspace_error(error))),
4020        };
4021        let replacement = LazyWorkspaceState {
4022            revision: prepared.revision.saturating_add(1),
4023            overlay,
4024            shadows: if result.is_ok() {
4025                prepared.shadows
4026            } else {
4027                prepared
4028                    .pending_remove
4029                    .as_ref()
4030                    .ok_or(LazyWorkspaceError::Concurrent)?
4031                    .prior_shadows
4032            },
4033            pending_remove: None,
4034            ..prepared.clone()
4035        };
4036        if !self
4037            .store
4038            .compare_and_swap_lazy_workspace(self.workspace.id(), prepared.revision, replacement)
4039            .await
4040            .map_err(store_error)?
4041        {
4042            return Err(LazyWorkspaceError::Concurrent);
4043        }
4044        result
4045    }
4046
4047    async fn state(&self) -> Result<LazyWorkspaceState, LazyWorkspaceError> {
4048        let state = self
4049            .store
4050            .load_lazy_workspace(self.workspace.id())
4051            .await
4052            .map_err(store_error)?
4053            .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
4054        if state.schema_version != LAZY_STATE_SCHEMA
4055            || state.workspace_id != self.workspace.id()
4056            || state.source != self.source.reference()
4057        {
4058            return Err(LazyWorkspaceError::StaleSource);
4059        }
4060        if state.pending_remove.is_some() {
4061            return Err(LazyWorkspaceError::Concurrent);
4062        }
4063        Ok(state)
4064    }
4065
4066    async fn state_measured(
4067        &self,
4068        budget: WorkBudget,
4069        cancellation: &CancellationToken,
4070    ) -> Result<OperationReceipt<LazyWorkspaceState>, LazyWorkspaceError> {
4071        let receipt = self
4072            .store
4073            .load_lazy_workspace_measured(self.workspace.id(), budget, cancellation)
4074            .await?;
4075        let state = receipt
4076            .value
4077            .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
4078        if state.schema_version != LAZY_STATE_SCHEMA
4079            || state.workspace_id != self.workspace.id()
4080            || state.source != self.source.reference()
4081        {
4082            return Err(LazyWorkspaceError::StaleSource);
4083        }
4084        if state.pending_remove.is_some() {
4085            return Err(LazyWorkspaceError::Concurrent);
4086        }
4087        Ok(OperationReceipt {
4088            value: state,
4089            work: receipt.work,
4090        })
4091    }
4092
4093    async fn recover_pending_remove(&self) -> Result<(), LazyWorkspaceError> {
4094        for _ in 0..MAXIMUM_STATE_RETRIES {
4095            let state = self
4096                .store
4097                .load_lazy_workspace(self.workspace.id())
4098                .await
4099                .map_err(store_error)?
4100                .ok_or_else(|| LazyWorkspaceError::Store("lazy binding is absent".to_owned()))?;
4101            if state.schema_version != LAZY_STATE_SCHEMA
4102                || state.workspace_id != self.workspace.id()
4103                || state.source.identity != self.source.reference().identity
4104            {
4105                return Err(LazyWorkspaceError::StaleSource);
4106            }
4107            let Some(pending) = state.pending_remove.as_ref() else {
4108                return Ok(());
4109            };
4110            let committed = match pending.kind {
4111                PendingLazyRemoveKind::SourceOnly => true,
4112                PendingLazyRemoveKind::Authored { idempotency_key }
4113                | PendingLazyRemoveKind::Mounted { idempotency_key } => self
4114                    .workspace
4115                    .operation_generation(idempotency_key)
4116                    .await
4117                    .map_err(workspace_error)?
4118                    .is_some(),
4119            };
4120            let replacement = LazyWorkspaceState {
4121                revision: state.revision.saturating_add(1),
4122                overlay: if committed {
4123                    pending.prepared_overlay
4124                } else {
4125                    pending.prior_overlay
4126                },
4127                shadows: if committed {
4128                    state.shadows
4129                } else {
4130                    pending.prior_shadows
4131                },
4132                pending_remove: None,
4133                ..state.clone()
4134            };
4135            if self
4136                .store
4137                .compare_and_swap_lazy_workspace(self.workspace.id(), state.revision, replacement)
4138                .await
4139                .map_err(store_error)?
4140            {
4141                return Ok(());
4142            }
4143        }
4144        Err(LazyWorkspaceError::Concurrent)
4145    }
4146
4147    async fn observe_measured(
4148        &self,
4149        path: &str,
4150        state: LazyWorkspaceState,
4151        cancellation: &CancellationToken,
4152    ) -> Result<OperationReceipt<(SourceReference, SourceNode)>, LazyWorkspaceError> {
4153        let path = self.canonical_path(path)?;
4154        if let Some(change) = self.overlay_fact(state.overlay, &path).await? {
4155            return match change {
4156                LazyOverlayChange::Observe { source, node } if source == state.source => {
4157                    Ok(OperationReceipt {
4158                        value: (source, node),
4159                        work: WorkCounters::default(),
4160                    })
4161                }
4162                LazyOverlayChange::Observe { .. } => {
4163                    self.observe_live_measured(path, state, cancellation).await
4164                }
4165                LazyOverlayChange::Tombstone => Err(LazyWorkspaceError::NotFound),
4166            };
4167        }
4168        self.observe_live_measured(path, state, cancellation).await
4169    }
4170
4171    async fn observe_live_measured(
4172        &self,
4173        path: String,
4174        state: LazyWorkspaceState,
4175        cancellation: &CancellationToken,
4176    ) -> Result<OperationReceipt<(SourceReference, SourceNode)>, LazyWorkspaceError> {
4177        let receipt = self
4178            .source
4179            .lookup(state.source, &self.namespace_path(&path)?, cancellation)
4180            .await
4181            .map_err(|failure| failure.error)?;
4182        let node = receipt.value.ok_or(LazyWorkspaceError::NotFound)?;
4183        self.append_overlay(
4184            path,
4185            LazyOverlayChange::Observe {
4186                source: state.source,
4187                node,
4188            },
4189        )
4190        .await?;
4191        Ok(OperationReceipt {
4192            value: (state.source, node),
4193            work: receipt.work,
4194        })
4195    }
4196
4197    async fn append_overlay(
4198        &self,
4199        path: String,
4200        change: LazyOverlayChange,
4201    ) -> Result<(), LazyWorkspaceError> {
4202        for _ in 0..MAXIMUM_STATE_RETRIES {
4203            let state = self.state().await?;
4204            let overlay_id = self
4205                .insert_overlay(state.overlay, path.clone(), change.clone())
4206                .await?;
4207            let replacement = LazyWorkspaceState {
4208                revision: state.revision.saturating_add(1),
4209                overlay: overlay_id,
4210                ..state.clone()
4211            };
4212            if self
4213                .store
4214                .compare_and_swap_lazy_workspace(self.workspace.id(), state.revision, replacement)
4215                .await
4216                .map_err(store_error)?
4217            {
4218                return Ok(());
4219            }
4220        }
4221        Err(LazyWorkspaceError::Concurrent)
4222    }
4223
4224    fn insert_overlay<'a>(
4225        &'a self,
4226        root: LazyOverlayId,
4227        path: String,
4228        change: LazyOverlayChange,
4229    ) -> LazyFuture<'a, Result<LazyOverlayId, LazyWorkspaceError>> {
4230        Box::pin(async move {
4231            insert_immutable_treap(&OverlayTreap(&self.store), root, path, change).await
4232        })
4233    }
4234
4235    async fn overlay_fact(
4236        &self,
4237        root: LazyOverlayId,
4238        path: &str,
4239    ) -> Result<Option<LazyOverlayChange>, LazyWorkspaceError> {
4240        immutable_treap_value(&OverlayTreap(&self.store), root, &path.to_owned()).await
4241    }
4242
4243    async fn overlay_fact_measured(
4244        &self,
4245        mut root: LazyOverlayId,
4246        path: &str,
4247        budget: WorkBudget,
4248        cancellation: &CancellationToken,
4249    ) -> Result<OperationReceipt<Option<LazyOverlayChange>>, LazyWorkspaceError> {
4250        let mut work = WorkCounters::default();
4251        loop {
4252            let receipt = self
4253                .store
4254                .load_lazy_overlay_measured(root, remaining_work(work, budget)?, cancellation)
4255                .await?;
4256            work = account_work(work, receipt.work, budget)?;
4257            let Some(node) = receipt.value else {
4258                return Ok(OperationReceipt { value: None, work });
4259            };
4260            let LazyOverlay::Node {
4261                path: node_path,
4262                change,
4263                left,
4264                right,
4265                ..
4266            } = node
4267            else {
4268                return Ok(OperationReceipt { value: None, work });
4269            };
4270            match path.cmp(node_path.as_str()) {
4271                std::cmp::Ordering::Equal => {
4272                    return Ok(OperationReceipt {
4273                        value: Some(change),
4274                        work,
4275                    });
4276                }
4277                std::cmp::Ordering::Less => root = left,
4278                std::cmp::Ordering::Greater => root = right,
4279            }
4280        }
4281    }
4282
4283    async fn shadow_record_measured(
4284        &self,
4285        mut root: LazyShadowId,
4286        file_id: FileId,
4287        budget: WorkBudget,
4288        cancellation: &CancellationToken,
4289    ) -> Result<OperationReceipt<Option<(FileRecord, crate::WorkspaceMetadata)>>, LazyWorkspaceError>
4290    {
4291        let mut work = WorkCounters::default();
4292        loop {
4293            let receipt = self
4294                .store
4295                .load_lazy_shadow_measured(root, remaining_work(work, budget)?, cancellation)
4296                .await?;
4297            work = account_work(work, receipt.work, budget)?;
4298            let Some(node) = receipt.value else {
4299                return Ok(OperationReceipt { value: None, work });
4300            };
4301            let LazyShadow::Node {
4302                file_id: node_file_id,
4303                record,
4304                metadata,
4305                left,
4306                right,
4307                ..
4308            } = node
4309            else {
4310                return Ok(OperationReceipt { value: None, work });
4311            };
4312            match file_id.cmp(&node_file_id) {
4313                std::cmp::Ordering::Equal => {
4314                    let record = crate::kernel::decode_file_record(&record)
4315                        .map_err(|error| LazyWorkspaceError::Store(error.to_string()))?;
4316                    return Ok(OperationReceipt {
4317                        value: Some((record, *metadata)),
4318                        work,
4319                    });
4320                }
4321                std::cmp::Ordering::Less => root = left,
4322                std::cmp::Ordering::Greater => root = right,
4323            }
4324        }
4325    }
4326
4327    fn insert_shadow<'a>(
4328        &'a self,
4329        root: LazyShadowId,
4330        file_id: FileId,
4331        record: FileRecord,
4332        metadata: crate::WorkspaceMetadata,
4333    ) -> LazyFuture<'a, Result<LazyShadowId, LazyWorkspaceError>> {
4334        Box::pin(async move {
4335            insert_immutable_treap(
4336                &ShadowTreap(&self.store),
4337                root,
4338                file_id,
4339                ShadowValue {
4340                    record: crate::kernel::encode_file_record(record),
4341                    metadata: Box::new(metadata),
4342                },
4343            )
4344            .await
4345        })
4346    }
4347
4348    async fn tombstoned_measured(
4349        &self,
4350        overlay_id: LazyOverlayId,
4351        path: &str,
4352        budget: WorkBudget,
4353        cancellation: &CancellationToken,
4354    ) -> Result<OperationReceipt<bool>, LazyWorkspaceError> {
4355        let path = self.canonical_path(path)?;
4356        let path_bytes = u64::try_from(path.capacity())
4357            .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4358        let mut work = WorkCounters {
4359            bytes_copied: u64::try_from(path.len())
4360                .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4361            allocation_operations: u64::from(path_bytes != 0),
4362            peak_allocation_bytes: path_bytes,
4363            ..WorkCounters::default()
4364        };
4365        work.verify(budget)
4366            .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4367        let ancestor_request = path.len();
4368        if ancestor_request != 0 {
4369            let requested = u64::try_from(ancestor_request)
4370                .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4371            let mut attempted = work
4372                .checked_add(WorkCounters {
4373                    bytes_copied: requested,
4374                    allocation_operations: 1,
4375                    ..WorkCounters::default()
4376                })
4377                .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4378            attempted.peak_allocation_bytes = attempted.peak_allocation_bytes.max(
4379                path_bytes
4380                    .checked_add(requested)
4381                    .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4382            );
4383            attempted
4384                .verify(budget)
4385                .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4386            work = attempted;
4387        }
4388        let root = self
4389            .overlay_fact_measured(overlay_id, "/", remaining_work(work, budget)?, cancellation)
4390            .await?;
4391        work = account_work(work, root.work, budget)?;
4392        if matches!(root.value, Some(LazyOverlayChange::Tombstone)) {
4393            return Ok(OperationReceipt { value: true, work });
4394        }
4395        let mut ancestor = String::new();
4396        ancestor
4397            .try_reserve_exact(ancestor_request)
4398            .map_err(|_| LazyWorkspaceError::Work("tombstone path allocation failed".to_owned()))?;
4399        for component in path.trim_start_matches('/').split('/') {
4400            if component.is_empty() {
4401                continue;
4402            }
4403            ancestor.push('/');
4404            ancestor.push_str(component);
4405            let fact = self
4406                .overlay_fact_measured(
4407                    overlay_id,
4408                    &ancestor,
4409                    remaining_work(work, budget)?,
4410                    cancellation,
4411                )
4412                .await?;
4413            work = account_work(work, fact.work, budget)?;
4414            if matches!(fact.value, Some(LazyOverlayChange::Tombstone)) {
4415                return Ok(OperationReceipt { value: true, work });
4416            }
4417        }
4418        Ok(OperationReceipt { value: false, work })
4419    }
4420
4421    fn canonical_path(&self, path: &str) -> Result<String, LazyWorkspaceError> {
4422        PortablePath::parse(path, self.workspace.limits())
4423            .map(|path| path.as_str().to_owned())
4424            .map_err(|error| LazyWorkspaceError::Workspace(error.to_string()))
4425    }
4426
4427    pub(crate) fn namespace_path(&self, path: &str) -> Result<NamespacePath, LazyWorkspaceError> {
4428        let portable = PortablePath::parse(path, self.workspace.limits())
4429            .map_err(|error| LazyWorkspaceError::Workspace(error.to_string()))?;
4430        NamespacePath::from_portable_in_profile(
4431            &portable,
4432            self.workspace.profile(),
4433            self.workspace.limits(),
4434        )
4435        .map_err(|error| LazyWorkspaceError::Workspace(error.to_string()))
4436    }
4437}
4438
4439fn store_error(error: impl std::fmt::Display) -> LazyWorkspaceError {
4440    LazyWorkspaceError::Store(error.to_string())
4441}
4442
4443fn lazy_store_read_work() -> WorkCounters {
4444    WorkCounters {
4445        object_probes: 1,
4446        backend_read_operations: 1,
4447        items_examined: 1,
4448        ..WorkCounters::default()
4449    }
4450}
4451
4452fn lazy_store_write_work() -> WorkCounters {
4453    WorkCounters {
4454        backend_write_operations: 1,
4455        items_examined: 1,
4456        ..WorkCounters::default()
4457    }
4458}
4459
4460fn measured_lazy_store_read(
4461    budget: WorkBudget,
4462    cancellation: &CancellationToken,
4463) -> Result<(), LazyWorkspaceError> {
4464    cancellation
4465        .check()
4466        .map_err(|_| LazyWorkspaceError::Cancelled)?;
4467    lazy_store_read_work()
4468        .verify(budget)
4469        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))
4470}
4471
4472fn measured_lazy_store_write(
4473    budget: WorkBudget,
4474    cancellation: &CancellationToken,
4475) -> Result<(), LazyWorkspaceError> {
4476    cancellation
4477        .check()
4478        .map_err(|_| LazyWorkspaceError::Cancelled)?;
4479    lazy_store_write_work()
4480        .verify(budget)
4481        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))
4482}
4483
4484fn workspace_error(error: WorkspaceError) -> LazyWorkspaceError {
4485    match error {
4486        WorkspaceError::StaleIdentity => LazyWorkspaceError::StaleIdentity,
4487        WorkspaceError::Cancelled(_) => LazyWorkspaceError::Cancelled,
4488        WorkspaceError::Work(error) => LazyWorkspaceError::Work(error.to_string()),
4489        error => LazyWorkspaceError::Workspace(error.to_string()),
4490    }
4491}
4492
4493fn source_kind(kind: crate::kernel::FileKind) -> SourceNodeKind {
4494    match kind {
4495        crate::kernel::FileKind::Directory => SourceNodeKind::Directory,
4496        crate::kernel::FileKind::Regular => SourceNodeKind::RegularFile,
4497        crate::kernel::FileKind::SymbolicLink => SourceNodeKind::SymbolicLink,
4498        crate::kernel::FileKind::Fifo => SourceNodeKind::Fifo,
4499        crate::kernel::FileKind::Socket => SourceNodeKind::Socket,
4500        crate::kernel::FileKind::CharacterDevice => SourceNodeKind::CharacterDevice,
4501        crate::kernel::FileKind::BlockDevice => SourceNodeKind::BlockDevice,
4502        crate::kernel::FileKind::ReparsePoint | crate::kernel::FileKind::MountBoundary => {
4503            SourceNodeKind::Unsupported
4504        }
4505    }
4506}
4507
4508fn record_logical_bytes(record: FileRecord) -> Option<u64> {
4509    match record.payload {
4510        FilePayload::InlineRegular(bytes) => u64::try_from(bytes.as_bytes().len()).ok(),
4511        FilePayload::Regular { logical_bytes, .. }
4512        | FilePayload::SymbolicLink {
4513            target_bytes: logical_bytes,
4514            ..
4515        }
4516        | FilePayload::ReparsePoint {
4517            payload_bytes: logical_bytes,
4518            ..
4519        } => Some(logical_bytes),
4520        FilePayload::Directory { .. } | FilePayload::Empty | FilePayload::Device { .. } => None,
4521    }
4522}
4523
4524fn source_file_metadata(source: crate::demand::SourceMetadata) -> FileMetadata {
4525    let time = |value: Option<i128>| {
4526        value
4527            .and_then(|value| i64::try_from(value).ok())
4528            .map_or(MetadataField::Unavailable, MetadataField::Value)
4529    };
4530    FileMetadata {
4531        posix_mode: metadata_u32(source.posix_mode),
4532        posix_uid: metadata_u32(source.posix_uid),
4533        posix_gid: metadata_u32(source.posix_gid),
4534        posix_flags: metadata_u64(source.posix_flags),
4535        windows_attributes: metadata_u32(source.windows_attributes),
4536        created_ns: time(source.created_ns),
4537        modified_ns: time(source.modified_ns),
4538        accessed_ns: time(source.accessed_ns),
4539        changed_ns: time(source.changed_ns),
4540        ..FileMetadata::default()
4541    }
4542}
4543
4544fn metadata_u32(value: Option<u32>) -> MetadataField<u32> {
4545    value.map_or(MetadataField::Unavailable, MetadataField::Value)
4546}
4547
4548fn metadata_u64(value: Option<u64>) -> MetadataField<u64> {
4549    value.map_or(MetadataField::Unavailable, MetadataField::Value)
4550}
4551
4552fn path_priority(path: &str) -> u64 {
4553    let digest = blake3::hash(path.as_bytes());
4554    let mut bytes = [0_u8; 8];
4555    if let Some(prefix) = digest.as_bytes().get(..8) {
4556        bytes.copy_from_slice(prefix);
4557    }
4558    u64::from_be_bytes(bytes)
4559}
4560
4561fn shadow_priority(file_id: FileId) -> u64 {
4562    let digest = blake3::hash(&file_id.into_bytes());
4563    let mut bytes = [0_u8; 8];
4564    bytes.copy_from_slice(&digest.as_bytes()[..8]);
4565    u64::from_le_bytes(bytes)
4566}
4567
4568fn parent_path(path: &str) -> Option<&str> {
4569    let index = path.rfind('/')?;
4570    if index == 0 {
4571        Some("/")
4572    } else {
4573        path.get(..index)
4574    }
4575}
4576
4577fn authored_entry(entry: WorkspaceDirectoryEntry) -> LazyDirectoryEntry {
4578    LazyDirectoryEntry {
4579        name: entry.name,
4580        kind: source_kind(entry.kind),
4581        authored: true,
4582    }
4583}
4584
4585fn logical_child_path(parent: &str, name: &LogicalName) -> Option<String> {
4586    let component = match name.encoding() {
4587        NameEncoding::Utf8 | NameEncoding::PosixBytes => {
4588            std::str::from_utf8(name.as_bytes()).ok()?.to_owned()
4589        }
4590        NameEncoding::WindowsUtf16Le => {
4591            let units = name
4592                .as_bytes()
4593                .chunks_exact(2)
4594                .filter_map(|pair| pair.try_into().ok().map(u16::from_le_bytes));
4595            char::decode_utf16(units)
4596                .collect::<Result<String, _>>()
4597                .ok()?
4598        }
4599    };
4600    Some(if parent == "/" {
4601        format!("/{component}")
4602    } else {
4603        format!("{parent}/{component}")
4604    })
4605}
4606
4607fn exactify_path_order(left: &str, right: &str) -> std::cmp::Ordering {
4608    left.matches('/')
4609        .count()
4610        .cmp(&right.matches('/').count())
4611        .then_with(|| left.cmp(right))
4612}
4613
4614fn account_work(
4615    current: WorkCounters,
4616    additional: WorkCounters,
4617    budget: WorkBudget,
4618) -> Result<WorkCounters, LazyWorkspaceError> {
4619    let combined = current
4620        .checked_add(additional)
4621        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4622    combined
4623        .verify(budget)
4624        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4625    Ok(combined)
4626}
4627
4628fn account_nested_with_live_memory(
4629    current: WorkCounters,
4630    mut nested: WorkCounters,
4631    live_bytes: u64,
4632    budget: WorkBudget,
4633) -> Result<WorkCounters, LazyWorkspaceError> {
4634    let simultaneous_peak = live_bytes
4635        .checked_add(nested.peak_allocation_bytes)
4636        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4637    nested.peak_allocation_bytes = 0;
4638    let mut combined = current
4639        .checked_add(nested)
4640        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4641    combined.peak_allocation_bytes = combined.peak_allocation_bytes.max(simultaneous_peak);
4642    combined
4643        .verify(budget)
4644        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4645    Ok(combined)
4646}
4647
4648#[allow(clippy::too_many_arguments)]
4649fn queue_exactify_mutation(
4650    pending: &mut Vec<AuthoredMutation>,
4651    mutation: AuthoredMutation,
4652    pending_bytes: &mut u64,
4653    retained_bytes: &mut u64,
4654    work: &mut WorkCounters,
4655    budget: WorkBudget,
4656) -> Result<(), LazyWorkspaceError> {
4657    let (AuthoredMutation::CreateFileFromContent { path, .. }
4658    | AuthoredMutation::CreateDirectory { path, .. }) = &mutation
4659    else {
4660        return Err(LazyWorkspaceError::UnsupportedNode);
4661    };
4662    let name_bytes = path.components().iter().try_fold(0_u64, |sum, name| {
4663        sum.checked_add(u64::try_from(name.retained_bytes()).ok()?)
4664    });
4665    let path_bytes = u64::try_from(path.components().len())
4666        .ok()
4667        .and_then(|count| {
4668            count.checked_mul(u64::try_from(std::mem::size_of::<LogicalName>()).ok()?)
4669        })
4670        .and_then(|bytes| bytes.checked_add(name_bytes?))
4671        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4672    let item_bytes = u64::try_from(std::mem::size_of::<AuthoredMutation>())
4673        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4674    let growth = u64::from(pending.len() == pending.capacity())
4675        .checked_mul(item_bytes)
4676        .and_then(|bytes| bytes.checked_add(path_bytes))
4677        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4678    let prospective = retained_bytes
4679        .checked_add(growth)
4680        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4681    work.peak_allocation_bytes = work.peak_allocation_bytes.max(prospective);
4682    work.verify(budget)
4683        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4684    let old_capacity = pending.capacity();
4685    pending
4686        .try_reserve_exact(1)
4687        .map_err(|_| LazyWorkspaceError::Work("exactify mutation allocation failed".to_owned()))?;
4688    let new_items = pending.capacity().saturating_sub(old_capacity);
4689    let actual_growth = u64::try_from(new_items)
4690        .ok()
4691        .and_then(|items| items.checked_mul(item_bytes))
4692        .and_then(|bytes| bytes.checked_add(path_bytes))
4693        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4694    *pending_bytes = pending_bytes
4695        .checked_add(actual_growth)
4696        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4697    *retained_bytes = retained_bytes
4698        .checked_add(actual_growth)
4699        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4700    work.peak_allocation_bytes = work.peak_allocation_bytes.max(*retained_bytes);
4701    work.verify(budget)
4702        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4703    pending.push(mutation);
4704    Ok(())
4705}
4706
4707fn reserve_exactify_path_slot(
4708    paths: &mut Vec<String>,
4709    retained_bytes: &mut u64,
4710    work: &mut WorkCounters,
4711    budget: WorkBudget,
4712) -> Result<(), LazyWorkspaceError> {
4713    if paths.len() < paths.capacity() {
4714        return Ok(());
4715    }
4716    let item_bytes = u64::try_from(std::mem::size_of::<String>())
4717        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4718    let old_bytes = u64::try_from(paths.capacity())
4719        .ok()
4720        .and_then(|count| count.checked_mul(item_bytes))
4721        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4722    let target = paths
4723        .len()
4724        .checked_add(1)
4725        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4726    let requested_bytes = u64::try_from(target)
4727        .ok()
4728        .and_then(|count| count.checked_mul(item_bytes))
4729        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4730    let copied = u64::try_from(paths.len())
4731        .ok()
4732        .and_then(|count| count.checked_mul(item_bytes))
4733        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4734    let mut attempted = work
4735        .checked_add(WorkCounters {
4736            bytes_copied: copied,
4737            allocation_operations: 1,
4738            ..WorkCounters::default()
4739        })
4740        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4741    attempted.peak_allocation_bytes = attempted.peak_allocation_bytes.max(
4742        retained_bytes
4743            .checked_add(requested_bytes)
4744            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4745    );
4746    attempted
4747        .verify(budget)
4748        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4749    paths
4750        .try_reserve_exact(1)
4751        .map_err(|_| LazyWorkspaceError::Work("exactify path allocation failed".to_owned()))?;
4752    let new_bytes = u64::try_from(paths.capacity())
4753        .ok()
4754        .and_then(|count| count.checked_mul(item_bytes))
4755        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4756    *retained_bytes = retained_bytes
4757        .checked_sub(old_bytes)
4758        .and_then(|value| value.checked_add(new_bytes))
4759        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4760    attempted.peak_allocation_bytes = attempted.peak_allocation_bytes.max(*retained_bytes);
4761    attempted
4762        .verify(budget)
4763        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4764    *work = attempted;
4765    Ok(())
4766}
4767
4768fn account_transient_string(
4769    work: WorkCounters,
4770    value: &String,
4771    live_bytes: u64,
4772    budget: WorkBudget,
4773) -> Result<WorkCounters, LazyWorkspaceError> {
4774    let bytes = u64::try_from(value.capacity())
4775        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4776    let copied = u64::try_from(value.len())
4777        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4778    let mut delta = WorkCounters {
4779        bytes_copied: copied,
4780        allocation_operations: u64::from(bytes != 0),
4781        ..WorkCounters::default()
4782    };
4783    delta.peak_allocation_bytes = live_bytes
4784        .checked_add(bytes)
4785        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4786    account_work(work, delta, budget)
4787}
4788
4789fn retained_directory_page_bytes(
4790    capacity: usize,
4791    entry_bytes: usize,
4792    mut names: impl Iterator<Item = usize>,
4793) -> Result<u64, LazyWorkspaceError> {
4794    let slots = capacity
4795        .checked_mul(entry_bytes)
4796        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4797    let names = names
4798        .try_fold(0_usize, |total, bytes| total.checked_add(bytes))
4799        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4800    u64::try_from(
4801        slots
4802            .checked_add(names)
4803            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4804    )
4805    .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))
4806}
4807
4808fn reserve_lazy_directory_page(
4809    entries: &mut Vec<LazyDirectoryEntry>,
4810    count: usize,
4811    temporary_bytes: u64,
4812    work: &mut WorkCounters,
4813    budget: WorkBudget,
4814) -> Result<u64, LazyWorkspaceError> {
4815    let requested = count
4816        .checked_mul(std::mem::size_of::<LazyDirectoryEntry>())
4817        .and_then(|bytes| u64::try_from(bytes).ok())
4818        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4819    let mut attempted = work
4820        .checked_add(WorkCounters {
4821            allocation_operations: u64::from(count != 0),
4822            ..WorkCounters::default()
4823        })
4824        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4825    attempted.peak_allocation_bytes = attempted.peak_allocation_bytes.max(
4826        temporary_bytes
4827            .checked_add(requested)
4828            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4829    );
4830    attempted
4831        .verify(budget)
4832        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4833    entries
4834        .try_reserve_exact(count)
4835        .map_err(|_| LazyWorkspaceError::Work("directory page allocation failed".to_owned()))?;
4836    let allocated = entries
4837        .capacity()
4838        .checked_mul(std::mem::size_of::<LazyDirectoryEntry>())
4839        .and_then(|bytes| u64::try_from(bytes).ok())
4840        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4841    attempted.peak_allocation_bytes = attempted.peak_allocation_bytes.max(
4842        temporary_bytes
4843            .checked_add(allocated)
4844            .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4845    );
4846    attempted
4847        .verify(budget)
4848        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))?;
4849    *work = attempted;
4850    temporary_bytes
4851        .checked_add(allocated)
4852        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))
4853}
4854
4855fn exactify_path_vector_bytes(paths: &Vec<String>) -> Result<u64, LazyWorkspaceError> {
4856    u64::try_from(paths.capacity())
4857        .ok()
4858        .and_then(|count| count.checked_mul(u64::try_from(std::mem::size_of::<String>()).ok()?))
4859        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))
4860}
4861
4862fn retain_exactify_string(
4863    value: &String,
4864    retained_bytes: &mut u64,
4865    work: &mut WorkCounters,
4866    budget: WorkBudget,
4867) -> Result<(), LazyWorkspaceError> {
4868    let capacity = u64::try_from(value.capacity())
4869        .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4870    let mut delta = WorkCounters {
4871        bytes_copied: u64::try_from(value.len())
4872            .map_err(|_| LazyWorkspaceError::Work("counter overflow".to_owned()))?,
4873        allocation_operations: u64::from(capacity != 0),
4874        ..WorkCounters::default()
4875    };
4876    *retained_bytes = retained_bytes
4877        .checked_add(capacity)
4878        .ok_or_else(|| LazyWorkspaceError::Work("counter overflow".to_owned()))?;
4879    delta.peak_allocation_bytes = *retained_bytes;
4880    *work = account_work(*work, delta, budget)?;
4881    Ok(())
4882}
4883
4884fn remaining_work(
4885    current: WorkCounters,
4886    budget: WorkBudget,
4887) -> Result<WorkBudget, LazyWorkspaceError> {
4888    current
4889        .remaining(budget)
4890        .map_err(|error| LazyWorkspaceError::Work(error.to_string()))
4891}
4892
4893fn exactify_error(stage: &str, path: &str, error: LazyWorkspaceError) -> LazyWorkspaceError {
4894    match error {
4895        LazyWorkspaceError::Cancelled | LazyWorkspaceError::Demand(DemandError::Cancelled) => {
4896            LazyWorkspaceError::Cancelled
4897        }
4898        error @ LazyWorkspaceError::Work(_) => error,
4899        error => {
4900            LazyWorkspaceError::Workspace(format!("exactify could not {stage} {path}: {error}"))
4901        }
4902    }
4903}
4904
4905fn exactify_batch_key(snapshot_id: LazySnapshotId, root: &str, index: u64) -> IdempotencyKey {
4906    let mut hasher = blake3::Hasher::new();
4907    hasher.update(b"acyclic-fs-lazy-exactify-batch-v1\0");
4908    hasher.update(&snapshot_id.into_bytes());
4909    hasher.update(root.as_bytes());
4910    hasher.update(&index.to_le_bytes());
4911    let mut bytes = [0; 16];
4912    bytes.copy_from_slice(&hasher.finalize().as_bytes()[..16]);
4913    IdempotencyKey::from_bytes(bytes)
4914}
4915
4916#[cfg(test)]
4917#[allow(clippy::expect_used, clippy::panic)]
4918mod tests {
4919    use super::*;
4920    use crate::demand::{
4921        DemandResult, SourceCursor, SourceDirectoryEntry, SourceDirectoryPage, SourceMetadata,
4922        SourceVersion,
4923    };
4924    use crate::performance::{OperationFailure, OperationReceipt, WorkCounters};
4925    use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
4926
4927    #[derive(Default)]
4928    struct SourceCounts {
4929        lookups: AtomicUsize,
4930        pages: AtomicUsize,
4931        ranges: AtomicUsize,
4932    }
4933
4934    struct CountingSource {
4935        identity: [u8; 16],
4936        epoch: AtomicU64,
4937        node: SourceNode,
4938        versions: Mutex<BTreeMap<u64, Bytes>>,
4939        directory_entries: usize,
4940        counts: SourceCounts,
4941        cancel_on_page: AtomicBool,
4942        cancel_on_range: AtomicBool,
4943    }
4944
4945    impl CountingSource {
4946        fn new(bytes: Bytes) -> Self {
4947            let logical_bytes = bytes.len() as u64;
4948            let versions = BTreeMap::from([(3, bytes)]);
4949            Self {
4950                identity: [7; 16],
4951                epoch: AtomicU64::new(3),
4952                node: SourceNode {
4953                    kind: SourceNodeKind::RegularFile,
4954                    file_identity: [8; 32],
4955                    link_count: Some(1),
4956                    device: None,
4957                    logical_bytes: Some(logical_bytes),
4958                    version: SourceVersion([9; 32]),
4959                    metadata: SourceMetadata::default(),
4960                },
4961                versions: Mutex::new(versions),
4962                directory_entries: 1,
4963                counts: SourceCounts::default(),
4964                cancel_on_page: AtomicBool::new(false),
4965                cancel_on_range: AtomicBool::new(false),
4966            }
4967        }
4968
4969        fn receipt<T>(value: T) -> DemandResult<T> {
4970            Ok(OperationReceipt {
4971                value,
4972                work: WorkCounters::default(),
4973            })
4974        }
4975
4976        fn invalidate(&self) {
4977            let prior = self.epoch.fetch_add(1, Ordering::Relaxed);
4978            let mut versions = self
4979                .versions
4980                .lock()
4981                .unwrap_or_else(|poisoned| poisoned.into_inner());
4982            let bytes = versions.get(&prior).cloned().unwrap_or_default();
4983            versions.insert(prior + 1, bytes);
4984        }
4985
4986        fn replace(&self, bytes: Bytes) {
4987            let epoch = self.epoch.fetch_add(1, Ordering::Relaxed) + 1;
4988            self.versions
4989                .lock()
4990                .unwrap_or_else(|poisoned| poisoned.into_inner())
4991                .insert(epoch, bytes);
4992        }
4993
4994        fn versioned_node(&self, source: SourceReference) -> Option<SourceNode> {
4995            if source.identity != self.identity {
4996                return None;
4997            }
4998            let versions = self
4999                .versions
5000                .lock()
5001                .unwrap_or_else(|poisoned| poisoned.into_inner());
5002            let bytes = versions.get(&source.epoch)?;
5003            Some(SourceNode {
5004                logical_bytes: Some(bytes.len() as u64),
5005                version: SourceVersion(*blake3::hash(bytes).as_bytes()),
5006                ..self.node
5007            })
5008        }
5009
5010        fn with_directory_entries(mut self, entries: usize) -> Self {
5011            self.directory_entries = entries;
5012            self
5013        }
5014
5015        fn cancel_next_page(&self) {
5016            self.cancel_on_page.store(true, Ordering::Relaxed);
5017        }
5018
5019        fn cancel_next_range(&self) {
5020            self.cancel_on_range.store(true, Ordering::Relaxed);
5021        }
5022    }
5023
5024    #[async_trait]
5025    impl DemandSource for CountingSource {
5026        fn reference(&self) -> SourceReference {
5027            SourceReference {
5028                identity: self.identity,
5029                epoch: self.epoch.load(Ordering::Relaxed),
5030            }
5031        }
5032
5033        async fn lookup(
5034            &self,
5035            source: SourceReference,
5036            _path: &NamespacePath,
5037            _cancellation: &CancellationToken,
5038        ) -> DemandResult<Option<SourceNode>> {
5039            self.counts.lookups.fetch_add(1, Ordering::Relaxed);
5040            let Some(node) = self.versioned_node(source) else {
5041                return Err(OperationFailure::before_work(DemandError::StaleSource));
5042            };
5043            Self::receipt(Some(node))
5044        }
5045
5046        async fn list_page(
5047            &self,
5048            source: SourceReference,
5049            _directory: &NamespacePath,
5050            cursor: Option<SourceCursor>,
5051            _maximum_entries: u32,
5052            cancellation: &CancellationToken,
5053        ) -> DemandResult<SourceDirectoryPage> {
5054            self.counts.pages.fetch_add(1, Ordering::Relaxed);
5055            if self.cancel_on_page.swap(false, Ordering::Relaxed) {
5056                cancellation.cancel();
5057                return Err(OperationFailure::before_work(DemandError::Cancelled));
5058            }
5059            if self.versioned_node(source).is_none() {
5060                return Err(OperationFailure::before_work(DemandError::StaleSource));
5061            }
5062            if cursor.is_some() {
5063                return Err(OperationFailure::before_work(DemandError::StaleCursor));
5064            }
5065            let entries = if self.directory_entries == 1 {
5066                vec![SourceDirectoryEntry {
5067                    name: LogicalName::new(NameEncoding::Utf8, b"file.txt".to_vec(), 255)
5068                        .expect("name"),
5069                    kind: SourceNodeKind::RegularFile,
5070                }]
5071            } else {
5072                (0..self.directory_entries)
5073                    .map(|index| SourceDirectoryEntry {
5074                        name: LogicalName::new(
5075                            NameEncoding::Utf8,
5076                            format!("file-{index:04}.txt").into_bytes(),
5077                            255,
5078                        )
5079                        .expect("name"),
5080                        kind: SourceNodeKind::RegularFile,
5081                    })
5082                    .collect()
5083            };
5084            Self::receipt(SourceDirectoryPage {
5085                entries,
5086                next: None,
5087                version: SourceVersion([4; 32]),
5088            })
5089        }
5090
5091        async fn read_range(
5092            &self,
5093            source: SourceReference,
5094            _path: &NamespacePath,
5095            expected: SourceVersion,
5096            offset: u64,
5097            length: u64,
5098            cancellation: &CancellationToken,
5099        ) -> DemandResult<Bytes> {
5100            self.counts.ranges.fetch_add(1, Ordering::Relaxed);
5101            if self.cancel_on_range.swap(false, Ordering::Relaxed) {
5102                cancellation.cancel();
5103            }
5104            let Some(node) = self.versioned_node(source) else {
5105                return Err(OperationFailure::before_work(DemandError::StaleSource));
5106            };
5107            if expected != node.version {
5108                return Err(OperationFailure::before_work(DemandError::StaleVersion));
5109            }
5110            let versions = self
5111                .versions
5112                .lock()
5113                .unwrap_or_else(|poisoned| poisoned.into_inner());
5114            let bytes = versions
5115                .get(&source.epoch)
5116                .ok_or_else(|| OperationFailure::before_work(DemandError::StaleSource))?;
5117            let start = usize::try_from(offset)
5118                .unwrap_or(usize::MAX)
5119                .min(bytes.len());
5120            let end = start
5121                .saturating_add(usize::try_from(length).unwrap_or(usize::MAX))
5122                .min(bytes.len());
5123            Self::receipt(bytes.slice(start..end))
5124        }
5125
5126        async fn read_link(
5127            &self,
5128            _source: SourceReference,
5129            _path: &NamespacePath,
5130            _expected: SourceVersion,
5131            _cancellation: &CancellationToken,
5132        ) -> DemandResult<Bytes> {
5133            Err(OperationFailure::before_work(DemandError::InvalidRequest))
5134        }
5135    }
5136
5137    #[tokio::test]
5138    async fn exactify_threads_cancellation_into_nested_source_work() {
5139        let fs = Fs::memory();
5140        let source = Arc::new(CountingSource::new(Bytes::from_static(b"content")));
5141        let root = LazyWorkspace::attach(
5142            &fs,
5143            "cancelled-exactify",
5144            Arc::clone(&source),
5145            MemoryLazyWorkspaceStore::default(),
5146        )
5147        .await
5148        .expect("attach");
5149        let cancellation = CancellationToken::new();
5150        source.cancel_next_page();
5151
5152        assert!(matches!(
5153            root.exactify(WorkBudget::UNBOUNDED, &cancellation).await,
5154            Err(LazyWorkspaceError::Cancelled)
5155        ));
5156        assert!(cancellation.is_cancelled());
5157        assert_eq!(source.counts.pages.load(Ordering::Relaxed), 1);
5158        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 0);
5159        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 0);
5160    }
5161
5162    #[tokio::test]
5163    async fn exactify_budget_exhaustion_cannot_publish_the_current_path() {
5164        let fs = Fs::memory();
5165        let source = Arc::new(CountingSource::new(Bytes::from_static(b"content")));
5166        let root = LazyWorkspace::attach(
5167            &fs,
5168            "budgeted-exactify",
5169            source,
5170            MemoryLazyWorkspaceStore::default(),
5171        )
5172        .await
5173        .expect("attach");
5174        let before = root
5175            .workspace()
5176            .head()
5177            .await
5178            .expect("head before exactify")
5179            .id();
5180        let mut budget = WorkBudget::UNBOUNDED;
5181        budget.authority_records_appended = 0;
5182
5183        assert!(matches!(
5184            root.exactify(budget, &CancellationToken::new()).await,
5185            Err(LazyWorkspaceError::Workspace(_))
5186        ));
5187        assert_eq!(
5188            root.workspace()
5189                .head()
5190                .await
5191                .expect("head after rejection")
5192                .id(),
5193            before
5194        );
5195        assert!(matches!(
5196            root.workspace().stat("/file.txt").await,
5197            Err(WorkspaceError::NotFound)
5198        ));
5199    }
5200
5201    #[tokio::test]
5202    async fn exactify_snapshot_rejects_before_destination_publication() {
5203        let fs = Fs::memory();
5204        let root = LazyWorkspace::attach(
5205            &fs,
5206            "snapshot-source",
5207            Arc::new(CountingSource::new(Bytes::from_static(b"content"))),
5208            MemoryLazyWorkspaceStore::default(),
5209        )
5210        .await
5211        .expect("attach");
5212        let snapshot = root.snapshot().await.expect("snapshot");
5213
5214        let cancelled = CancellationToken::new();
5215        cancelled.cancel();
5216        assert!(matches!(
5217            root.exactify_snapshot(
5218                snapshot,
5219                "cancelled-destination",
5220                IdempotencyKey::from_bytes([0x91; 16]),
5221                WorkBudget::UNBOUNDED,
5222                &cancelled,
5223            )
5224            .await,
5225            Err(LazyWorkspaceError::Cancelled)
5226        ));
5227        assert!(fs.open_workspace("cancelled-destination").await.is_err());
5228
5229        assert!(matches!(
5230            root.exactify_snapshot(
5231                snapshot,
5232                "budget-destination",
5233                IdempotencyKey::from_bytes([0x92; 16]),
5234                WorkBudget::default(),
5235                &CancellationToken::new(),
5236            )
5237            .await,
5238            Err(LazyWorkspaceError::Work(_))
5239        ));
5240        assert!(fs.open_workspace("budget-destination").await.is_err());
5241    }
5242
5243    #[tokio::test]
5244    async fn full_directory_page_peak_is_admitted_before_projection() {
5245        let fs = Fs::memory();
5246        let root = LazyWorkspace::attach(
5247            &fs,
5248            "page-memory",
5249            Arc::new(
5250                CountingSource::new(Bytes::from_static(b"content")).with_directory_entries(1_024),
5251            ),
5252            MemoryLazyWorkspaceStore::default(),
5253        )
5254        .await
5255        .expect("attach");
5256        let receipt = root
5257            .list_directory_measured(
5258                "/",
5259                None,
5260                1_024,
5261                WorkBudget::UNBOUNDED,
5262                &CancellationToken::new(),
5263                None,
5264                None,
5265            )
5266            .await
5267            .expect("measured page");
5268        assert_eq!(receipt.value.entries.len(), 1_024);
5269        assert!(receipt.work.allocation_operations >= 1);
5270
5271        let mut insufficient = WorkBudget::UNBOUNDED;
5272        insufficient.peak_allocation_bytes = receipt.work.peak_allocation_bytes - 1;
5273        assert!(matches!(
5274            root.list_directory_measured(
5275                "/",
5276                None,
5277                1_024,
5278                insufficient,
5279                &CancellationToken::new(),
5280                None,
5281                None,
5282            )
5283            .await,
5284            Err(LazyWorkspaceError::Work(_))
5285        ));
5286    }
5287
5288    #[tokio::test]
5289    async fn attach_open_and_fork_demand_no_source_work() {
5290        let fs = Fs::memory();
5291        let source = Arc::new(CountingSource::new(Bytes::from_static(b"abcdef")));
5292        let store = MemoryLazyWorkspaceStore::default();
5293        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store.clone())
5294            .await
5295            .expect("attach");
5296        let reopened = LazyWorkspace::open(
5297            fs.open_workspace("root").await.expect("workspace"),
5298            Arc::clone(&source),
5299            store.clone(),
5300        )
5301        .await
5302        .expect("open");
5303        let child = reopened
5304            .fork("child", IdempotencyKey::from_bytes([5; 16]))
5305            .await
5306            .expect("fork");
5307        let first_snapshot = child.snapshot().await.expect("snapshot");
5308        let second_snapshot = child.snapshot().await.expect("repeat snapshot");
5309        let resumed_child = root
5310            .open_related(fs.open_workspace("child").await.expect("child workspace"))
5311            .await
5312            .expect("resume related child");
5313        let sibling = root
5314            .fork("sibling", IdempotencyKey::from_bytes([6; 16]))
5315            .await
5316            .expect("sibling fork");
5317        let resumed_sibling = resumed_child
5318            .open_related(
5319                fs.open_workspace("sibling")
5320                    .await
5321                    .expect("sibling workspace"),
5322            )
5323            .await
5324            .expect("constant-work branch switch");
5325        assert_eq!(
5326            sibling.snapshot().await.expect("sibling snapshot"),
5327            resumed_sibling
5328                .snapshot()
5329                .await
5330                .expect("resumed sibling snapshot")
5331        );
5332
5333        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 0);
5334        assert_eq!(source.counts.pages.load(Ordering::Relaxed), 0);
5335        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 0);
5336        assert_eq!(
5337            child
5338                .state()
5339                .await
5340                .expect("child state")
5341                .parent_workspace_id,
5342            Some(root.workspace().id())
5343        );
5344        assert_eq!(first_snapshot, second_snapshot);
5345    }
5346
5347    #[cfg(feature = "native-mount")]
5348    #[tokio::test]
5349    async fn mounted_promotion_cancels_midstream_without_installing_a_partial_candidate() {
5350        let fs = Fs::memory();
5351        let source = Arc::new(CountingSource::new(Bytes::from(vec![
5352            0x5a;
5353            2 * 1024 * 1024
5354        ])));
5355        let root = LazyWorkspace::attach(
5356            &fs,
5357            "cancelled-mount-promotion",
5358            Arc::clone(&source),
5359            MemoryLazyWorkspaceStore::default(),
5360        )
5361        .await
5362        .expect("attach");
5363        let head = root.workspace().head().await.expect("head").id();
5364        let mut checkout = root
5365            .workspace()
5366            .checkout(
5367                crate::model::GenerationSelector::Head,
5368                crate::model::CheckoutMode::tracking_transaction(),
5369            )
5370            .await
5371            .expect("checkout");
5372        let LazyLookup::Source(node) = root.lookup("/file.txt").await.expect("source") else {
5373            panic!("expected unresolved source file");
5374        };
5375        let cancellation = CancellationToken::new();
5376        source.cancel_next_range();
5377        assert!(matches!(
5378            root.stage_exact_into_checkout(
5379                &mut checkout,
5380                "/file.txt",
5381                root.source_file_id(&node),
5382                u64::MAX,
5383                &cancellation,
5384            )
5385            .await,
5386            Err(LazyWorkspaceError::Cancelled)
5387        ));
5388        assert!(cancellation.is_cancelled());
5389        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 1);
5390        assert_eq!(root.workspace().head().await.expect("head").id(), head);
5391        assert!(
5392            checkout
5393                .lookup_no_follow(
5394                    &root.namespace_path("/file.txt").expect("path"),
5395                    WorkBudget::UNBOUNDED,
5396                    &CancellationToken::new(),
5397                )
5398                .await
5399                .expect("candidate lookup")
5400                .value
5401                .record
5402                .is_none()
5403        );
5404    }
5405
5406    #[cfg(feature = "native-mount")]
5407    #[tokio::test]
5408    async fn mounted_promotion_stays_in_the_dirty_candidate_until_one_publication() {
5409        let fs = Fs::memory();
5410        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
5411        let root = LazyWorkspace::attach(
5412            &fs,
5413            "staged-mount-promotion",
5414            source,
5415            MemoryLazyWorkspaceStore::default(),
5416        )
5417        .await
5418        .expect("attach");
5419        let original_head = root.workspace().head().await.expect("head").id();
5420        let mut checkout = root
5421            .workspace()
5422            .checkout(
5423                crate::model::GenerationSelector::Head,
5424                crate::model::CheckoutMode::tracking_transaction(),
5425            )
5426            .await
5427            .expect("mount checkout");
5428        let other = root.namespace_path("/other.txt").expect("other path");
5429        checkout
5430            .apply_authored_transaction(
5431                vec![AuthoredMutation::CreateFile {
5432                    path: other.clone(),
5433                    bytes: Bytes::from_static(b"dirty"),
5434                    metadata: FileMetadata::default(),
5435                }],
5436                WorkBudget::UNBOUNDED,
5437                &CancellationToken::new(),
5438            )
5439            .await
5440            .expect("dirty mounted write");
5441        let LazyLookup::Source(node) = root.lookup("/file.txt").await.expect("source") else {
5442            panic!("expected unresolved source file");
5443        };
5444        let cancelled = CancellationToken::new();
5445        cancelled.cancel();
5446        assert!(matches!(
5447            root.stage_exact_into_checkout(
5448                &mut checkout,
5449                "/file.txt",
5450                root.source_file_id(&node),
5451                u64::MAX,
5452                &cancelled,
5453            )
5454            .await,
5455            Err(LazyWorkspaceError::Cancelled)
5456        ));
5457        assert!(
5458            root.stage_exact_into_checkout(
5459                &mut checkout,
5460                "/file.txt",
5461                root.source_file_id(&node),
5462                u64::MAX,
5463                &CancellationToken::new(),
5464            )
5465            .await
5466            .expect("stage source in dirty mount")
5467        );
5468        assert!(
5469            !root
5470                .stage_exact_into_checkout(
5471                    &mut checkout,
5472                    "/file.txt",
5473                    root.source_file_id(&node),
5474                    u64::MAX,
5475                    &CancellationToken::new(),
5476                )
5477                .await
5478                .expect("already staged source identity")
5479        );
5480        assert_eq!(
5481            root.workspace()
5482                .head()
5483                .await
5484                .expect("unpublished head")
5485                .id(),
5486            original_head
5487        );
5488        let file = root.namespace_path("/file.txt").expect("file path");
5489        for path in [&other, &file] {
5490            assert!(
5491                checkout
5492                    .lookup_no_follow(path, WorkBudget::UNBOUNDED, &CancellationToken::new())
5493                    .await
5494                    .expect("candidate lookup")
5495                    .value
5496                    .record
5497                    .is_some()
5498            );
5499        }
5500        checkout
5501            .commit(
5502                crate::OperationId::new(),
5503                WorkBudget::UNBOUNDED,
5504                &CancellationToken::new(),
5505            )
5506            .await
5507            .expect("publish both changes once");
5508        assert_ne!(
5509            root.workspace().head().await.expect("published head").id(),
5510            original_head
5511        );
5512    }
5513
5514    #[cfg(feature = "native-mount")]
5515    #[tokio::test]
5516    async fn mounted_alias_promotions_preserve_source_hard_links_before_publication() {
5517        let fs = Fs::memory();
5518        let mut source = CountingSource::new(Bytes::from_static(b"shared"));
5519        source.node.link_count = Some(2);
5520        let root = LazyWorkspace::attach(
5521            &fs,
5522            "staged-hard-link-promotions",
5523            Arc::new(source),
5524            MemoryLazyWorkspaceStore::default(),
5525        )
5526        .await
5527        .expect("attach");
5528        let mut checkout = root
5529            .workspace()
5530            .checkout(
5531                crate::model::GenerationSelector::Head,
5532                crate::model::CheckoutMode::tracking_transaction(),
5533            )
5534            .await
5535            .expect("mount checkout");
5536        for path in ["/a", "/b"] {
5537            let LazyLookup::Source(node) = root.lookup(path).await.expect("source alias") else {
5538                panic!("expected unresolved source alias");
5539            };
5540            assert!(
5541                root.stage_exact_into_checkout(
5542                    &mut checkout,
5543                    path,
5544                    root.source_file_id(&node),
5545                    u64::MAX,
5546                    &CancellationToken::new(),
5547                )
5548                .await
5549                .expect("stage source alias")
5550            );
5551        }
5552        let first = checkout
5553            .lookup_no_follow(
5554                &root.namespace_path("/a").expect("first path"),
5555                WorkBudget::UNBOUNDED,
5556                &CancellationToken::new(),
5557            )
5558            .await
5559            .expect("first alias")
5560            .value
5561            .record
5562            .expect("first record");
5563        let second = checkout
5564            .lookup_no_follow(
5565                &root.namespace_path("/b").expect("second path"),
5566                WorkBudget::UNBOUNDED,
5567                &CancellationToken::new(),
5568            )
5569            .await
5570            .expect("second alias")
5571            .value
5572            .record
5573            .expect("second record");
5574        assert_eq!(first.file_id, second.file_id);
5575        assert_eq!(first.link_count, 2);
5576        assert_eq!(second.link_count, 2);
5577    }
5578
5579    #[cfg(feature = "native-mount")]
5580    #[tokio::test]
5581    async fn mounted_directory_pages_include_unpublished_checkout_bindings() {
5582        let fs = Fs::memory();
5583        let root = LazyWorkspace::attach(
5584            &fs,
5585            "mounted-directory-candidate",
5586            Arc::new(CountingSource::new(Bytes::from_static(b"source"))),
5587            MemoryLazyWorkspaceStore::default(),
5588        )
5589        .await
5590        .expect("attach");
5591        let mut checkout = root
5592            .workspace()
5593            .checkout(
5594                crate::model::GenerationSelector::Head,
5595                crate::model::CheckoutMode::tracking_transaction(),
5596            )
5597            .await
5598            .expect("mount checkout");
5599        checkout
5600            .apply_authored_transaction(
5601                vec![AuthoredMutation::CreateFile {
5602                    path: root.namespace_path("/new.txt").expect("new path"),
5603                    bytes: Bytes::from_static(b"new"),
5604                    metadata: FileMetadata::default(),
5605                }],
5606                WorkBudget::UNBOUNDED,
5607                &CancellationToken::new(),
5608            )
5609            .await
5610            .expect("unpublished file");
5611        let source_page = root
5612            .list_directory_in_checkout(&mut checkout, "/", None, 16, None)
5613            .await
5614            .expect("source page");
5615        assert_eq!(source_page.entries.len(), 1);
5616        assert!(!source_page.entries[0].authored);
5617        let authored_page = root
5618            .list_directory_in_checkout(&mut checkout, "/", source_page.next, 16, None)
5619            .await
5620            .expect("authored page");
5621        assert_eq!(authored_page.entries.len(), 1);
5622        assert!(authored_page.entries[0].authored);
5623        assert_eq!(authored_page.entries[0].name.as_bytes(), b"new.txt");
5624        assert!(authored_page.next.is_none());
5625    }
5626
5627    #[cfg(all(
5628        feature = "native-mount",
5629        feature = "native-watch",
5630        not(target_arch = "wasm32")
5631    ))]
5632    #[tokio::test]
5633    async fn mounted_replacement_directory_does_not_expose_obsolete_source_children() {
5634        use crate::demand::native::NativeDemandSource;
5635        use crate::model::{FilesystemProfile, VolumeLimits};
5636
5637        let directory = tempfile::tempdir().expect("temporary source");
5638        std::fs::create_dir(directory.path().join("d")).expect("source directory");
5639        std::fs::write(directory.path().join("d").join("old.txt"), b"old").expect("source child");
5640        let source = Arc::new(
5641            NativeDemandSource::open(
5642                directory.path(),
5643                FilesystemProfile::Portable,
5644                VolumeLimits::default(),
5645            )
5646            .await
5647            .expect("source"),
5648        );
5649        let fs = Fs::memory();
5650        let root = LazyWorkspace::attach(
5651            &fs,
5652            "mounted-directory-replacement",
5653            source,
5654            MemoryLazyWorkspaceStore::default(),
5655        )
5656        .await
5657        .expect("attach");
5658        let mut checkout = root
5659            .workspace()
5660            .checkout(
5661                crate::model::GenerationSelector::Head,
5662                crate::model::CheckoutMode::tracking_transaction(),
5663            )
5664            .await
5665            .expect("mount checkout");
5666        assert_eq!(
5667            root.list_directory_in_checkout(&mut checkout, "/d", None, 16, None)
5668                .await
5669                .expect("source directory")
5670                .entries
5671                .len(),
5672            1
5673        );
5674        checkout
5675            .apply_authored_transaction(
5676                vec![AuthoredMutation::CreateDirectory {
5677                    path: root.namespace_path("/d").expect("replacement path"),
5678                    metadata: FileMetadata::default(),
5679                }],
5680                WorkBudget::UNBOUNDED,
5681                &CancellationToken::new(),
5682            )
5683            .await
5684            .expect("replace with empty authored directory");
5685        let page = root
5686            .list_directory_in_checkout(&mut checkout, "/d", None, 16, None)
5687            .await
5688            .expect("replacement directory");
5689        assert!(page.entries.is_empty());
5690        assert!(page.next.is_none());
5691    }
5692
5693    #[tokio::test]
5694    async fn source_only_remove_does_not_advance_the_authored_head() {
5695        let fs = Fs::memory();
5696        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
5697        let root = LazyWorkspace::attach(
5698            &fs,
5699            "source-only-remove-effect",
5700            source,
5701            MemoryLazyWorkspaceStore::default(),
5702        )
5703        .await
5704        .expect("attach");
5705        let original_head = root.workspace().head().await.expect("head").id();
5706        let LazyLookup::Source(node) = root.lookup("/file.txt").await.expect("source") else {
5707            panic!("expected source file");
5708        };
5709        root.remove_if("/file.txt", Some(root.source_file_id(&node)))
5710            .await
5711            .expect("source-only tombstone");
5712        assert_eq!(
5713            root.workspace().head().await.expect("unchanged head").id(),
5714            original_head
5715        );
5716        assert!(matches!(
5717            root.lookup("/file.txt").await,
5718            Err(LazyWorkspaceError::NotFound)
5719        ));
5720    }
5721
5722    #[cfg(feature = "native-mount")]
5723    #[tokio::test]
5724    async fn mounted_whiteout_follows_the_checkout_publication_decision() {
5725        let fs = Fs::memory();
5726        let root = LazyWorkspace::attach(
5727            &fs,
5728            "mounted-whiteout-publication",
5729            Arc::new(CountingSource::new(Bytes::from_static(b"source"))),
5730            MemoryLazyWorkspaceStore::default(),
5731        )
5732        .await
5733        .expect("attach");
5734        let paths = vec!["/file.txt".to_owned()];
5735        let aborted = IdempotencyKey::new();
5736        root.prepare_mount_removals(&paths, &[], aborted)
5737            .await
5738            .expect("prepare uncommitted whiteout");
5739        assert!(!root.finish_mount_removals().await.expect("rollback"));
5740        assert!(matches!(
5741            root.lookup("/file.txt").await.expect("source remains"),
5742            LazyLookup::Source(_)
5743        ));
5744
5745        let committed = IdempotencyKey::new();
5746        root.prepare_mount_removals(&paths, &[], committed)
5747            .await
5748            .expect("prepare committed whiteout");
5749        let mut checkout = root
5750            .workspace()
5751            .checkout(
5752                crate::model::GenerationSelector::Head,
5753                crate::model::CheckoutMode::tracking_transaction(),
5754            )
5755            .await
5756            .expect("checkout");
5757        assert!(matches!(
5758            checkout
5759                .commit_with_permit_even_if_clean(
5760                    committed.operation_id(),
5761                    crate::PublicationPermit::Unrestricted,
5762                    WorkBudget::UNBOUNDED,
5763                    &CancellationToken::new(),
5764                )
5765                .await
5766                .expect("publish unchanged authored root")
5767                .value,
5768            crate::CheckoutCommitOutcome::Committed { .. }
5769        ));
5770        assert!(root.finish_mount_removals().await.expect("finalize"));
5771        assert!(matches!(
5772            root.lookup("/file.txt").await,
5773            Err(LazyWorkspaceError::NotFound)
5774        ));
5775    }
5776
5777    #[tokio::test]
5778    async fn exact_observation_is_shared_and_content_is_range_demanded() {
5779        let fs = Fs::memory();
5780        let source = Arc::new(CountingSource::new(Bytes::from_static(b"abcdef")));
5781        let store = MemoryLazyWorkspaceStore::default();
5782        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store)
5783            .await
5784            .expect("attach");
5785
5786        assert_eq!(
5787            root.stat("/deep/file.txt")
5788                .await
5789                .expect("stat")
5790                .logical_bytes,
5791            Some(6)
5792        );
5793        assert_eq!(
5794            root.stat("/deep/file.txt")
5795                .await
5796                .expect("cached stat")
5797                .logical_bytes,
5798            Some(6)
5799        );
5800        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1);
5801        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 0);
5802        assert_eq!(
5803            root.read_range("/deep/file.txt", 2, 3)
5804                .await
5805                .expect("range"),
5806            Bytes::from_static(b"cde")
5807        );
5808        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 1);
5809        assert_eq!(source.counts.pages.load(Ordering::Relaxed), 0);
5810    }
5811
5812    #[tokio::test]
5813    async fn inspection_is_transient_and_resolved_reads_pin_the_source_version() {
5814        let fs = Fs::memory();
5815        let source = Arc::new(CountingSource::new(Bytes::from_static(b"before")));
5816        let root = LazyWorkspace::attach(
5817            &fs,
5818            "transient-inspection",
5819            Arc::clone(&source),
5820            MemoryLazyWorkspaceStore::default(),
5821        )
5822        .await
5823        .expect("attach");
5824        let before = root.state().await.expect("state before inspection");
5825
5826        let (lookup, source_reference) = root
5827            .inspect_resolved("/file.txt")
5828            .await
5829            .expect("inspect source file");
5830        let LazyLookup::Source(node) = lookup else {
5831            panic!("inspection must resolve the source file");
5832        };
5833        assert_eq!(root.state().await.expect("state after inspection"), before);
5834
5835        source.replace(Bytes::from_static(b"after"));
5836        assert_eq!(
5837            root.read_source_range(
5838                "/file.txt",
5839                source_reference.expect("source reference"),
5840                node,
5841                0,
5842                6,
5843            )
5844            .await
5845            .expect("read pinned version"),
5846            Bytes::from_static(b"before")
5847        );
5848
5849        root.rebind_source().await.expect("rebind source");
5850        let LazyLookup::Source(rebound) = root.inspect("/file.txt").await.expect("reinspect")
5851        else {
5852            panic!("rebound inspection must resolve the source file");
5853        };
5854        assert_ne!(rebound.version, node.version);
5855    }
5856
5857    #[tokio::test]
5858    async fn independent_alias_promotions_preserve_source_hard_links() {
5859        let fs = Fs::memory();
5860        let mut source = CountingSource::new(Bytes::from_static(b"shared"));
5861        source.node.link_count = Some(2);
5862        source.node.metadata.posix_mode = Some(0o100640);
5863        source.node.metadata.modified_ns = Some(123_456);
5864        let source = Arc::new(source);
5865        let root = LazyWorkspace::attach(
5866            &fs,
5867            "hard-link-promotions",
5868            Arc::clone(&source),
5869            MemoryLazyWorkspaceStore::default(),
5870        )
5871        .await
5872        .expect("attach");
5873
5874        root.promote("/a", 64, IdempotencyKey::from_bytes([0x71; 16]))
5875            .await
5876            .expect("promote first alias");
5877        root.promote("/b", 64, IdempotencyKey::from_bytes([0x72; 16]))
5878            .await
5879            .expect("promote second alias");
5880        let first = root.workspace().stat("/a").await.expect("first stat");
5881        let second = root.workspace().stat("/b").await.expect("second stat");
5882        assert_eq!(first.file_id, second.file_id);
5883        assert_eq!(first.file_id, root.source_file_id(&source.node));
5884        assert_eq!(first.link_count, 2);
5885        assert_eq!(second.link_count, 2);
5886        assert_eq!(first.metadata.posix_mode, Some(0o100640));
5887        assert_eq!(second.metadata.modified_ns, Some(123_456));
5888
5889        root.write("/a", Bytes::from_static(b"changed"))
5890            .await
5891            .expect("mutate alias");
5892        assert_eq!(
5893            root.workspace()
5894                .read("/b", 64)
5895                .await
5896                .expect("read other alias")
5897                .as_ref(),
5898            b"changed"
5899        );
5900    }
5901
5902    #[tokio::test]
5903    async fn unresolved_alias_with_unknown_link_count_reads_latest_authored_identity() {
5904        let fs = Fs::memory();
5905        let mut source = CountingSource::new(Bytes::from_static(b"source"));
5906        source.node.link_count = None;
5907        let source = Arc::new(source);
5908        let root = LazyWorkspace::attach(
5909            &fs,
5910            "hard-link-late-alias",
5911            Arc::clone(&source),
5912            MemoryLazyWorkspaceStore::default(),
5913        )
5914        .await
5915        .expect("attach");
5916
5917        root.promote("/a", 64, IdempotencyKey::from_bytes([0x73; 16]))
5918            .await
5919            .expect("promote first alias");
5920        root.write("/a", Bytes::from_static(b"changed"))
5921            .await
5922            .expect("mutate first alias");
5923        let source_reads = source.counts.ranges.load(Ordering::Relaxed);
5924        assert_eq!(
5925            root.read("/b", 64).await.expect("read alias").as_ref(),
5926            b"changed"
5927        );
5928        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), source_reads);
5929
5930        root.promote("/b", 64, IdempotencyKey::from_bytes([0x74; 16]))
5931            .await
5932            .expect("promote late alias");
5933        let first = root.workspace().stat("/a").await.expect("first stat");
5934        let second = root.workspace().stat("/b").await.expect("second stat");
5935        assert_eq!(first.file_id, second.file_id);
5936        assert_eq!(first.file_id, root.source_file_id(&source.node));
5937        assert_eq!(
5938            root.read("/b", 64)
5939                .await
5940                .expect("read promoted alias")
5941                .as_ref(),
5942            b"changed"
5943        );
5944    }
5945
5946    #[tokio::test]
5947    async fn identity_shadow_survives_last_promoted_alias_removal() {
5948        let fs = Fs::memory();
5949        let mut source = CountingSource::new(Bytes::from_static(b"source"));
5950        source.node.link_count = Some(2);
5951        let source = Arc::new(source);
5952        let store = MemoryLazyWorkspaceStore::default();
5953        let root =
5954            LazyWorkspace::attach(&fs, "hard-link-shadow", Arc::clone(&source), store.clone())
5955                .await
5956                .expect("attach");
5957
5958        root.write("/a", Bytes::from_static(b"changed"))
5959            .await
5960            .expect("direct identity-preserving write");
5961        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 0);
5962        root.remove("/a").await.expect("remove promoted alias");
5963        assert_eq!(
5964            root.read("/b", 64)
5965                .await
5966                .expect("read shadow alias")
5967                .as_ref(),
5968            b"changed"
5969        );
5970
5971        let reopened = LazyWorkspace::open(
5972            fs.open_workspace("hard-link-shadow")
5973                .await
5974                .expect("workspace"),
5975            source,
5976            store,
5977        )
5978        .await
5979        .expect("reopen");
5980        assert_eq!(
5981            reopened
5982                .read("/b", 64)
5983                .await
5984                .expect("reopened shadow")
5985                .as_ref(),
5986            b"changed"
5987        );
5988    }
5989
5990    #[tokio::test]
5991    async fn remove_if_rejects_a_stale_source_identity() {
5992        let fs = Fs::memory();
5993        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
5994        let root = LazyWorkspace::attach(
5995            &fs,
5996            "stale-remove",
5997            Arc::clone(&source),
5998            MemoryLazyWorkspaceStore::default(),
5999        )
6000        .await
6001        .expect("attach");
6002        let stale = FileId::from_bytes([0xff; 16]);
6003        assert!(matches!(
6004            root.remove_if("/file.txt", Some(stale)).await,
6005            Err(LazyWorkspaceError::StaleIdentity)
6006        ));
6007        assert!(root.stat("/file.txt").await.is_ok());
6008    }
6009
6010    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6011    #[tokio::test]
6012    async fn metadata_inspection_does_not_stale_a_native_directory_cursor() {
6013        use crate::demand::native::NativeDemandSource;
6014        use crate::model::{FilesystemProfile, VolumeLimits};
6015
6016        let directory = tempfile::tempdir().expect("temporary source");
6017        std::fs::write(directory.path().join("a.txt"), b"a").expect("first file");
6018        std::fs::write(directory.path().join("b.txt"), b"b").expect("second file");
6019        let source = Arc::new(
6020            NativeDemandSource::open(
6021                directory.path(),
6022                FilesystemProfile::Portable,
6023                VolumeLimits::default(),
6024            )
6025            .await
6026            .expect("source"),
6027        );
6028        let fs = Fs::memory();
6029        let root = LazyWorkspace::attach(
6030            &fs,
6031            "paged-source",
6032            source,
6033            MemoryLazyWorkspaceStore::default(),
6034        )
6035        .await
6036        .expect("attach");
6037        let first = root.list_directory("/", None, 1).await.expect("first page");
6038        let name = std::str::from_utf8(first.entries[0].name.as_bytes()).expect("portable name");
6039        root.inspect(&format!("/{name}"))
6040            .await
6041            .expect("entry metadata");
6042        let second = root
6043            .list_directory("/", first.next, 1)
6044            .await
6045            .expect("second page remains valid");
6046        assert_eq!(second.entries.len(), 1);
6047    }
6048
6049    #[tokio::test]
6050    async fn unresolved_aliases_have_one_stable_identity_and_dense_seek_needs_no_content() {
6051        let fs = Fs::memory();
6052        let source = Arc::new(CountingSource::new(Bytes::from_static(b"abcdef")));
6053        let root = LazyWorkspace::attach(
6054            &fs,
6055            "root",
6056            Arc::clone(&source),
6057            MemoryLazyWorkspaceStore::default(),
6058        )
6059        .await
6060        .expect("attach");
6061        let first = match root.lookup("/first").await.expect("first") {
6062            LazyLookup::Source(node) => node,
6063            LazyLookup::Authored { .. } | LazyLookup::Shadow { .. } => {
6064                panic!("source lookup expected")
6065            }
6066        };
6067        let second = match root.lookup("/second").await.expect("second") {
6068            LazyLookup::Source(node) => node,
6069            LazyLookup::Authored { .. } | LazyLookup::Shadow { .. } => {
6070                panic!("source lookup expected")
6071            }
6072        };
6073        assert_eq!(root.source_file_id(&first), root.source_file_id(&second));
6074        assert_eq!(
6075            root.seek("/first", 2, LazySeekTarget::Data)
6076                .await
6077                .expect("seek data"),
6078            Some(2)
6079        );
6080        assert_eq!(
6081            root.seek("/first", 2, LazySeekTarget::Hole)
6082                .await
6083                .expect("seek hole"),
6084            Some(6)
6085        );
6086        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 0);
6087    }
6088
6089    #[tokio::test]
6090    async fn promotion_demands_only_the_selected_file_and_is_idempotent() {
6091        let fs = Fs::memory();
6092        let bytes = Bytes::from(vec![b'x'; 1024 * 1024 + 17]);
6093        let source = Arc::new(CountingSource::new(bytes.clone()));
6094        let root = LazyWorkspace::attach(
6095            &fs,
6096            "root",
6097            Arc::clone(&source),
6098            MemoryLazyWorkspaceStore::default(),
6099        )
6100        .await
6101        .expect("attach");
6102        let key = IdempotencyKey::from_bytes([21; 16]);
6103
6104        let first = root
6105            .promote("/deep/file.txt", bytes.len() as u64, key)
6106            .await
6107            .expect("promote");
6108        assert!(matches!(first, TransactionCommit::Committed(_)));
6109        assert_eq!(
6110            root.workspace()
6111                .read("/deep/file.txt", bytes.len() as u64)
6112                .await
6113                .expect("authored content"),
6114            bytes
6115        );
6116        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1);
6117        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 2);
6118        assert_eq!(source.counts.pages.load(Ordering::Relaxed), 0);
6119
6120        let second = root
6121            .promote("/deep/file.txt", 0, key)
6122            .await
6123            .expect("idempotent promotion");
6124        assert!(matches!(second, TransactionCommit::AlreadyCommitted(_)));
6125        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1);
6126        assert_eq!(source.counts.ranges.load(Ordering::Relaxed), 2);
6127    }
6128
6129    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6130    #[tokio::test]
6131    async fn promoted_source_directory_keeps_its_identity_and_lazy_children() {
6132        use crate::demand::native::NativeDemandSource;
6133        use crate::model::{FilesystemProfile, VolumeLimits};
6134
6135        let directory = tempfile::tempdir().expect("temporary source");
6136        std::fs::create_dir(directory.path().join("repo")).expect("source directory");
6137        std::fs::write(
6138            directory.path().join("repo/HEAD"),
6139            b"ref: refs/heads/main\n",
6140        )
6141        .expect("source child");
6142        let source = Arc::new(
6143            NativeDemandSource::open(
6144                directory.path(),
6145                FilesystemProfile::Portable,
6146                VolumeLimits::default(),
6147            )
6148            .await
6149            .expect("source"),
6150        );
6151        let root = LazyWorkspace::attach(
6152            &Fs::memory(),
6153            "directory-identity",
6154            source,
6155            MemoryLazyWorkspaceStore::default(),
6156        )
6157        .await
6158        .expect("attach");
6159        let source_node = match root.lookup("/repo").await.expect("source directory") {
6160            LazyLookup::Source(node) => node,
6161            LazyLookup::Authored { .. } | LazyLookup::Shadow { .. } => {
6162                panic!("expected source directory")
6163            }
6164        };
6165        let source_id = root.source_file_id(&source_node);
6166
6167        root.promote("/repo", 0, IdempotencyKey::from_bytes([0x52; 16]))
6168            .await
6169            .expect("promote directory");
6170
6171        let promoted = root.lookup("/repo").await.expect("promoted directory");
6172        assert_eq!(
6173            root.stable_file_id_for_lookup("/repo", &promoted)
6174                .await
6175                .expect("stable projection identity"),
6176            source_id
6177        );
6178        assert_eq!(
6179            root.read("/repo/HEAD", 64).await.expect("lazy child"),
6180            Bytes::from_static(b"ref: refs/heads/main\n")
6181        );
6182    }
6183
6184    #[tokio::test]
6185    async fn exact_promotion_accepts_an_identity_preserving_direct_write() {
6186        let fs = Fs::memory();
6187        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6188        let root = LazyWorkspace::attach(
6189            &fs,
6190            "root",
6191            Arc::clone(&source),
6192            MemoryLazyWorkspaceStore::default(),
6193        )
6194        .await
6195        .expect("attach");
6196        let source_node = match root.lookup("/file.txt").await.expect("source lookup") {
6197            LazyLookup::Source(node) => node,
6198            LazyLookup::Authored { .. } | LazyLookup::Shadow { .. } => {
6199                panic!("expected source node")
6200            }
6201        };
6202        let expected_source = root.source_file_id(&source_node);
6203        root.write("/file.txt", Bytes::from_static(b"replacement"))
6204            .await
6205            .expect("authored replacement");
6206
6207        root.promote_exact(
6208            "/file.txt",
6209            expected_source,
6210            64,
6211            IdempotencyKey::from_bytes([0x73; 16]),
6212        )
6213        .await
6214        .expect("identity-preserving write is already promoted");
6215        assert_eq!(
6216            root.read("/file.txt", 64)
6217                .await
6218                .expect("replacement remains"),
6219            Bytes::from_static(b"replacement")
6220        );
6221    }
6222
6223    #[tokio::test]
6224    async fn authored_state_and_tombstones_mask_the_source() {
6225        let fs = Fs::memory();
6226        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6227        let root = LazyWorkspace::attach(
6228            &fs,
6229            "root",
6230            Arc::clone(&source),
6231            MemoryLazyWorkspaceStore::default(),
6232        )
6233        .await
6234        .expect("attach");
6235
6236        root.write("/file.txt", Bytes::from_static(b"authored"))
6237            .await
6238            .expect("write");
6239        assert_eq!(
6240            root.read("/file.txt", 32).await.expect("authored read"),
6241            Bytes::from_static(b"authored")
6242        );
6243        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1);
6244        root.remove("/file.txt").await.expect("remove");
6245        assert!(matches!(
6246            root.stat("/file.txt").await,
6247            Err(LazyWorkspaceError::NotFound)
6248        ));
6249        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1);
6250    }
6251
6252    #[tokio::test]
6253    async fn source_epoch_can_rebind_without_enumeration() {
6254        let fs = Fs::memory();
6255        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6256        let root = LazyWorkspace::attach(
6257            &fs,
6258            "root",
6259            Arc::clone(&source),
6260            MemoryLazyWorkspaceStore::default(),
6261        )
6262        .await
6263        .expect("attach");
6264        root.stat("/observed.txt").await.expect("observe");
6265        let before = root.snapshot().await.expect("snapshot before rebind");
6266        source.invalidate();
6267        assert!(matches!(
6268            root.stat("/new.txt").await,
6269            Err(LazyWorkspaceError::StaleSource)
6270        ));
6271        let rebound = root.rebind_source().await.expect("rebind");
6272        assert_eq!(rebound.source, source.reference());
6273        assert_eq!(source.counts.pages.load(Ordering::Relaxed), 0);
6274        assert_eq!(
6275            root.stat("/observed.txt")
6276                .await
6277                .expect("cached")
6278                .logical_bytes,
6279            Some(6)
6280        );
6281        let after = root.snapshot().await.expect("snapshot after refresh");
6282        assert_ne!(after, before);
6283        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 2);
6284    }
6285
6286    #[tokio::test]
6287    async fn snapshot_exactification_uses_live_epoch_for_unresolved_paths() {
6288        let fs = Fs::memory();
6289        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6290        let root = LazyWorkspace::attach(
6291            &fs,
6292            "root",
6293            Arc::clone(&source),
6294            MemoryLazyWorkspaceStore::default(),
6295        )
6296        .await
6297        .expect("attach");
6298        let snapshot = root.snapshot().await.expect("snapshot before rebind");
6299
6300        source.replace(Bytes::from_static(b"changed"));
6301        root.rebind_source().await.expect("rebind");
6302        let exact = root
6303            .exactify_snapshot(
6304                snapshot,
6305                "historical-exact",
6306                IdempotencyKey::from_bytes([23; 16]),
6307                WorkBudget::UNBOUNDED,
6308                &CancellationToken::new(),
6309            )
6310            .await
6311            .expect("exactify historical snapshot");
6312
6313        assert_eq!(
6314            exact
6315                .value
6316                .read("/file.txt", 32)
6317                .await
6318                .expect("unresolved file from live epoch"),
6319            Bytes::from_static(b"changed")
6320        );
6321    }
6322
6323    #[tokio::test]
6324    async fn snapshot_exactification_keeps_observed_paths_on_the_pinned_epoch() {
6325        let fs = Fs::memory();
6326        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6327        let root = LazyWorkspace::attach(
6328            &fs,
6329            "root",
6330            Arc::clone(&source),
6331            MemoryLazyWorkspaceStore::default(),
6332        )
6333        .await
6334        .expect("attach");
6335        root.stat("/file.txt").await.expect("observe old epoch");
6336        let snapshot = root.snapshot().await.expect("snapshot after observation");
6337
6338        source.replace(Bytes::from_static(b"changed"));
6339        root.rebind_source().await.expect("rebind");
6340        let exact = root
6341            .exactify_snapshot(
6342                snapshot,
6343                "pinned-exact",
6344                IdempotencyKey::from_bytes([24; 16]),
6345                WorkBudget::UNBOUNDED,
6346                &CancellationToken::new(),
6347            )
6348            .await
6349            .expect("exactify pinned observation");
6350
6351        assert_eq!(
6352            exact
6353                .value
6354                .read("/file.txt", 32)
6355                .await
6356                .expect("observed file from pinned epoch"),
6357            Bytes::from_static(b"source")
6358        );
6359    }
6360
6361    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6362    #[tokio::test]
6363    async fn native_source_rebind_refreshes_an_observed_file() {
6364        use crate::demand::native::NativeDemandSource;
6365        use crate::model::{FilesystemProfile, VolumeLimits};
6366
6367        let directory = tempfile::tempdir().expect("temporary source");
6368        let path = directory.path().join("file.txt");
6369        std::fs::write(&path, b"before").expect("initial file");
6370        let source = Arc::new(
6371            NativeDemandSource::open(
6372                directory.path(),
6373                FilesystemProfile::Portable,
6374                VolumeLimits::default(),
6375            )
6376            .await
6377            .expect("source"),
6378        );
6379        let fs = Fs::memory();
6380        let root = LazyWorkspace::attach(
6381            &fs,
6382            "native-rebind",
6383            Arc::clone(&source),
6384            MemoryLazyWorkspaceStore::default(),
6385        )
6386        .await
6387        .expect("attach");
6388        assert_eq!(
6389            root.read("/file.txt", 64).await.expect("initial read"),
6390            Bytes::from_static(b"before")
6391        );
6392        let before = root.snapshot().await.expect("snapshot before change");
6393
6394        std::fs::write(&path, b"after").expect("external write");
6395        source.invalidate();
6396        root.rebind_source().await.expect("rebind source");
6397
6398        assert_eq!(
6399            root.read("/file.txt", 64).await.expect("refreshed read"),
6400            Bytes::from_static(b"after")
6401        );
6402        assert_ne!(
6403            root.snapshot().await.expect("snapshot after change"),
6404            before
6405        );
6406    }
6407
6408    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6409    #[tokio::test]
6410    async fn exactify_is_bounded_and_returns_only_a_complete_generation() {
6411        use crate::demand::native::NativeDemandSource;
6412        use crate::model::{FilesystemProfile, VolumeLimits};
6413
6414        let directory = tempfile::tempdir().expect("temporary source");
6415        std::fs::create_dir(directory.path().join("nested")).expect("nested directory");
6416        std::fs::write(directory.path().join("a.txt"), b"alpha").expect("first file");
6417        std::fs::write(directory.path().join("nested").join("b.txt"), b"beta")
6418            .expect("second file");
6419        let source = Arc::new(
6420            NativeDemandSource::open(
6421                directory.path(),
6422                FilesystemProfile::Portable,
6423                VolumeLimits::default(),
6424            )
6425            .await
6426            .expect("source"),
6427        );
6428        let fs = Fs::memory();
6429        let root =
6430            LazyWorkspace::attach(&fs, "exactify", source, MemoryLazyWorkspaceStore::default())
6431                .await
6432                .expect("attach");
6433        let mut insufficient = WorkBudget::UNBOUNDED;
6434        insufficient.source_entries_visited = 1;
6435        assert!(matches!(
6436            root.exactify(insufficient, &CancellationToken::new()).await,
6437            Err(LazyWorkspaceError::Work(_))
6438        ));
6439
6440        let exact = root
6441            .exactify(WorkBudget::UNBOUNDED, &CancellationToken::new())
6442            .await
6443            .expect("exact capture");
6444        assert_eq!(
6445            exact
6446                .value
6447                .read("/nested/b.txt", 16)
6448                .await
6449                .expect("captured file"),
6450            Bytes::from_static(b"beta")
6451        );
6452        assert_eq!(exact.work.source_entries_visited, 3);
6453        assert_eq!(
6454            exact.work.source_bytes_read, 9,
6455            "version-pinned demand is read once and measured by the blob builder"
6456        );
6457    }
6458
6459    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6460    #[tokio::test]
6461    async fn exactify_subtree_keeps_siblings_lazy() {
6462        use crate::demand::native::NativeDemandSource;
6463        use crate::model::{FilesystemProfile, VolumeLimits};
6464
6465        let directory = tempfile::tempdir().expect("temporary source");
6466        std::fs::create_dir(directory.path().join("hot")).expect("hot directory");
6467        std::fs::create_dir(directory.path().join("cold")).expect("cold directory");
6468        std::fs::write(directory.path().join("hot/file.txt"), b"hot").expect("hot file");
6469        std::fs::write(directory.path().join("cold/file.txt"), b"cold").expect("cold file");
6470        let source = Arc::new(
6471            NativeDemandSource::open(
6472                directory.path(),
6473                FilesystemProfile::Portable,
6474                VolumeLimits::default(),
6475            )
6476            .await
6477            .expect("source"),
6478        );
6479        let root = LazyWorkspace::attach(
6480            &Fs::memory(),
6481            "subtree-exactify",
6482            source,
6483            MemoryLazyWorkspaceStore::default(),
6484        )
6485        .await
6486        .expect("attach");
6487
6488        let exact = root
6489            .exactify_subtree_with_permit(
6490                "/hot",
6491                WorkBudget::UNBOUNDED,
6492                &CancellationToken::new(),
6493                crate::PublicationPermit::Unrestricted,
6494            )
6495            .await
6496            .expect("capture hot subtree");
6497        assert_eq!(
6498            exact
6499                .value
6500                .read("/hot/file.txt", 16)
6501                .await
6502                .expect("hot content"),
6503            Bytes::from_static(b"hot")
6504        );
6505        #[cfg(feature = "native-mount")]
6506        {
6507            let staged = tempfile::tempdir().expect("empty native stage");
6508            exact
6509                .value
6510                .materialize_path(
6511                    "/hot",
6512                    &crate::MaterializeOptions::native(staged.path()),
6513                    WorkBudget::UNBOUNDED,
6514                    &CancellationToken::new(),
6515                )
6516                .await
6517                .expect("materialize only pinned hot subtree");
6518            assert_eq!(
6519                std::fs::read(staged.path().join("hot/file.txt")).expect("staged hot file"),
6520                b"hot"
6521            );
6522            assert!(!staged.path().join("cold").exists());
6523        }
6524        assert!(matches!(
6525            exact.value.read("/cold/file.txt", 16).await,
6526            Err(WorkspaceError::NotFound)
6527        ));
6528        assert!(matches!(
6529            root.lookup("/cold/file.txt")
6530                .await
6531                .expect("cold remains lazy"),
6532            LazyLookup::Source(_)
6533        ));
6534        assert!(matches!(
6535            root.exactify_subtree_with_permit(
6536                "/hot/file.txt",
6537                WorkBudget::UNBOUNDED,
6538                &CancellationToken::new(),
6539                crate::PublicationPermit::Unrestricted,
6540            )
6541            .await,
6542            Err(LazyWorkspaceError::NotDirectory)
6543        ));
6544    }
6545
6546    #[cfg(all(windows, feature = "native-watch"))]
6547    #[tokio::test]
6548    async fn source_page_batch_lookup_transcodes_windows_names_to_workspace_profile() {
6549        use crate::demand::native::NativeDemandSource;
6550        use crate::model::{FilesystemProfile, VolumeLimits};
6551
6552        let directory = tempfile::tempdir().expect("temporary source");
6553        std::fs::write(directory.path().join("café.txt"), b"source").expect("source file");
6554        let source = Arc::new(
6555            NativeDemandSource::open(
6556                directory.path(),
6557                FilesystemProfile::Windows,
6558                VolumeLimits::default(),
6559            )
6560            .await
6561            .expect("Windows source"),
6562        );
6563        let root = LazyWorkspace::attach(
6564            &Fs::memory(),
6565            "windows-source-portable-authored",
6566            source,
6567            MemoryLazyWorkspaceStore::default(),
6568        )
6569        .await
6570        .expect("attach portable authored workspace");
6571        assert_eq!(root.workspace().profile(), FilesystemProfile::Portable);
6572        let exact = root
6573            .exactify(WorkBudget::UNBOUNDED, &CancellationToken::new())
6574            .await
6575            .expect("exactify Windows source into portable authored workspace");
6576        assert_eq!(
6577            exact
6578                .value
6579                .read("/café.txt", 16)
6580                .await
6581                .expect("captured file"),
6582            Bytes::from_static(b"source")
6583        );
6584    }
6585
6586    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
6587    #[tokio::test]
6588    async fn exactify_preserves_hard_links_across_batch_boundaries() {
6589        use crate::demand::native::NativeDemandSource;
6590        use crate::model::{FilesystemProfile, VolumeLimits};
6591
6592        let directory = tempfile::tempdir().expect("temporary source");
6593        let source_path = directory.path().join("a000");
6594        std::fs::write(&source_path, b"shared").expect("source file");
6595        for index in 1..520 {
6596            std::fs::hard_link(&source_path, directory.path().join(format!("a{index:03}")))
6597                .expect("source hard link");
6598        }
6599        let source = Arc::new(
6600            NativeDemandSource::open(
6601                directory.path(),
6602                FilesystemProfile::Portable,
6603                VolumeLimits::default(),
6604            )
6605            .await
6606            .expect("source"),
6607        );
6608        let root = LazyWorkspace::attach(
6609            &Fs::memory(),
6610            "hard-link-batches",
6611            source,
6612            MemoryLazyWorkspaceStore::default(),
6613        )
6614        .await
6615        .expect("attach");
6616        let exact = root
6617            .exactify(WorkBudget::UNBOUNDED, &CancellationToken::new())
6618            .await
6619            .expect("exactify all batches");
6620        let first = exact.value.stat("/a000").await.expect("first stat");
6621        let last = exact.value.stat("/a519").await.expect("last stat");
6622        assert_eq!(first.file_id, last.file_id);
6623        assert_eq!(last.link_count, 520);
6624    }
6625
6626    #[tokio::test]
6627    async fn directory_pages_emit_authored_entries_once_after_source_phase() {
6628        let fs = Fs::memory();
6629        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6630        let root = LazyWorkspace::attach(
6631            &fs,
6632            "root",
6633            Arc::clone(&source),
6634            MemoryLazyWorkspaceStore::default(),
6635        )
6636        .await
6637        .expect("attach");
6638        root.write("/file.txt", Bytes::from_static(b"authored"))
6639            .await
6640            .expect("write");
6641
6642        let source_page = root
6643            .list_directory("/", None, 16)
6644            .await
6645            .expect("source phase");
6646        assert!(source_page.entries.is_empty());
6647        let authored_page = root
6648            .list_directory("/", source_page.next, 16)
6649            .await
6650            .expect("authored phase");
6651        assert_eq!(authored_page.entries.len(), 1);
6652        assert!(authored_page.entries[0].authored);
6653        assert!(authored_page.next.is_none());
6654    }
6655
6656    #[tokio::test]
6657    async fn directory_cursor_rejects_overlay_changes_between_pages() {
6658        let fs = Fs::memory();
6659        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6660        let root = LazyWorkspace::attach(
6661            &fs,
6662            "root",
6663            Arc::clone(&source),
6664            MemoryLazyWorkspaceStore::default(),
6665        )
6666        .await
6667        .expect("attach");
6668        let first = root
6669            .list_directory("/", None, 16)
6670            .await
6671            .expect("first page");
6672        root.stat("/newly-observed.txt").await.expect("observe");
6673
6674        assert!(matches!(
6675            root.list_directory("/", first.next, 16).await,
6676            Err(LazyWorkspaceError::StaleCursor)
6677        ));
6678    }
6679
6680    #[tokio::test]
6681    async fn failed_authored_remove_rolls_back_source_tombstone() {
6682        let fs = Fs::memory();
6683        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6684        let root = LazyWorkspace::attach(
6685            &fs,
6686            "root",
6687            Arc::clone(&source),
6688            MemoryLazyWorkspaceStore::default(),
6689        )
6690        .await
6691        .expect("attach");
6692        root.write("/directory/file.txt", Bytes::from_static(b"authored"))
6693            .await
6694            .expect("write");
6695        let before = root.state().await.expect("state");
6696
6697        assert!(matches!(
6698            root.remove("/directory").await,
6699            Err(LazyWorkspaceError::Workspace(_))
6700        ));
6701        let after = root.state().await.expect("state after failure");
6702        assert_eq!(after.overlay, before.overlay);
6703        assert_eq!(after.shadows, before.shadows);
6704        assert!(after.pending_remove.is_none());
6705        assert!(root.stat("/directory").await.expect("directory").authored);
6706    }
6707
6708    #[tokio::test]
6709    async fn open_migrates_released_schema_five_state() {
6710        let fs = Fs::memory();
6711        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6712        let store = MemoryLazyWorkspaceStore::default();
6713        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store.clone())
6714            .await
6715            .expect("attach");
6716        root.write("/file.txt", Bytes::from_static(b"authored"))
6717            .await
6718            .expect("write");
6719        let prior = root.state().await.expect("state");
6720        let mut idle = serde_json::to_value(&prior).expect("encode");
6721        idle["schema_version"] = LAZY_STATE_SCHEMA_V5.into();
6722        assert_eq!(
6723            serde_json::from_value::<LazyWorkspaceState>(idle).expect("idle schema 5"),
6724            prior
6725        );
6726
6727        let LazyLookup::Authored { stat, .. } = root.lookup("/file.txt").await.expect("authored")
6728        else {
6729            panic!("expected authored file");
6730        };
6731        let record = root
6732            .workspace()
6733            .record_by_id(stat.file_id)
6734            .await
6735            .expect("authored record");
6736        let staged_shadows = root
6737            .insert_shadow(prior.shadows, stat.file_id, record, stat.metadata)
6738            .await
6739            .expect("staged identity shadow");
6740        let tombstone = root
6741            .insert_overlay(
6742                prior.overlay,
6743                "/file.txt".to_owned(),
6744                LazyOverlayChange::Tombstone,
6745            )
6746            .await
6747            .expect("tombstone");
6748        let mut pending = serde_json::to_value(LazyWorkspaceState {
6749            revision: prior.revision + 1,
6750            overlay: tombstone,
6751            shadows: staged_shadows,
6752            pending_remove: None,
6753            ..prior.clone()
6754        })
6755        .expect("encode");
6756        pending["schema_version"] = LAZY_STATE_SCHEMA_V5.into();
6757        pending["pending_remove"] = serde_json::json!({
6758            "path": "/file.txt",
6759            "prior_overlay": prior.overlay,
6760            "tombstone_overlay": tombstone,
6761            "kind": PendingLazyRemoveKind::Authored {
6762                idempotency_key: IdempotencyKey::from_bytes([0x35; 16]),
6763            },
6764        });
6765        let pending = serde_json::from_value::<LazyWorkspaceState>(pending).expect("schema 5");
6766        assert!(
6767            store
6768                .compare_and_swap_lazy_workspace(root.workspace().id(), prior.revision, pending)
6769                .await
6770                .expect("prepare")
6771        );
6772
6773        let reopened = LazyWorkspace::open(
6774            fs.open_workspace("root").await.expect("workspace"),
6775            source,
6776            store,
6777        )
6778        .await
6779        .expect("reopen migrated state");
6780        let recovered = reopened.state().await.expect("recovered state");
6781        assert_eq!(recovered.schema_version, LAZY_STATE_SCHEMA);
6782        assert_eq!(recovered.overlay, prior.overlay);
6783        assert_eq!(recovered.shadows, staged_shadows);
6784        assert!(recovered.pending_remove.is_none());
6785        assert!(reopened.stat("/file.txt").await.expect("file").authored);
6786    }
6787
6788    #[tokio::test]
6789    async fn open_recovers_remove_intent_from_authored_state() {
6790        let fs = Fs::memory();
6791        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6792        let store = MemoryLazyWorkspaceStore::default();
6793        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store.clone())
6794            .await
6795            .expect("attach");
6796        root.write("/file.txt", Bytes::from_static(b"authored"))
6797            .await
6798            .expect("write");
6799        let prior = root.state().await.expect("state");
6800        let LazyLookup::Authored { stat, .. } = root.lookup("/file.txt").await.expect("authored")
6801        else {
6802            panic!("expected authored file");
6803        };
6804        let record = root
6805            .workspace()
6806            .record_by_id(stat.file_id)
6807            .await
6808            .expect("authored record");
6809        let staged_shadows = root
6810            .insert_shadow(prior.shadows, stat.file_id, record, stat.metadata)
6811            .await
6812            .expect("staged identity shadow");
6813        assert_ne!(staged_shadows, prior.shadows);
6814        let tombstone = root
6815            .insert_overlay(
6816                prior.overlay,
6817                "/file.txt".to_owned(),
6818                LazyOverlayChange::Tombstone,
6819            )
6820            .await
6821            .expect("tombstone");
6822        let first_key = IdempotencyKey::from_bytes([0x31; 16]);
6823        let pending = LazyWorkspaceState {
6824            revision: prior.revision + 1,
6825            overlay: tombstone,
6826            shadows: staged_shadows,
6827            pending_remove: Some(PendingLazyRemove {
6828                prior_overlay: prior.overlay,
6829                prior_shadows: prior.shadows,
6830                prepared_overlay: tombstone,
6831                kind: PendingLazyRemoveKind::Authored {
6832                    idempotency_key: first_key,
6833                },
6834            }),
6835            ..prior.clone()
6836        };
6837        assert!(
6838            store
6839                .compare_and_swap_lazy_workspace(root.workspace().id(), prior.revision, pending)
6840                .await
6841                .expect("prepare")
6842        );
6843
6844        let reopened = LazyWorkspace::open(
6845            fs.open_workspace("root").await.expect("workspace"),
6846            Arc::clone(&source),
6847            store.clone(),
6848        )
6849        .await
6850        .expect("recover before physical remove");
6851        let recovered = reopened.state().await.expect("recovered state");
6852        assert_eq!(recovered.overlay, prior.overlay);
6853        assert_eq!(recovered.shadows, prior.shadows);
6854        assert!(recovered.pending_remove.is_none());
6855
6856        let committed_key = IdempotencyKey::from_bytes([0x32; 16]);
6857        let prepared = LazyWorkspaceState {
6858            revision: recovered.revision + 1,
6859            overlay: tombstone,
6860            shadows: staged_shadows,
6861            pending_remove: Some(PendingLazyRemove {
6862                prior_overlay: recovered.overlay,
6863                prior_shadows: recovered.shadows,
6864                prepared_overlay: tombstone,
6865                kind: PendingLazyRemoveKind::Authored {
6866                    idempotency_key: committed_key,
6867                },
6868            }),
6869            ..recovered.clone()
6870        };
6871        assert!(
6872            store
6873                .compare_and_swap_lazy_workspace(
6874                    reopened.workspace().id(),
6875                    recovered.revision,
6876                    prepared
6877                )
6878                .await
6879                .expect("prepare second")
6880        );
6881        let mut removal = reopened
6882            .workspace()
6883            .begin_transaction(committed_key)
6884            .await
6885            .expect("removal transaction");
6886        removal.remove("/file.txt").await.expect("stage remove");
6887        assert!(matches!(
6888            removal.commit().await.expect("physical remove"),
6889            TransactionCommit::Committed(_)
6890        ));
6891
6892        let finalized = LazyWorkspace::open(
6893            fs.open_workspace("root").await.expect("workspace"),
6894            Arc::clone(&source),
6895            store,
6896        )
6897        .await
6898        .expect("recover after physical remove");
6899        let final_state = finalized.state().await.expect("final state");
6900        assert_eq!(final_state.overlay, tombstone);
6901        assert_eq!(final_state.shadows, staged_shadows);
6902        assert!(matches!(
6903            finalized.stat("/file.txt").await,
6904            Err(LazyWorkspaceError::NotFound)
6905        ));
6906    }
6907
6908    #[tokio::test]
6909    async fn source_only_remove_recovery_keeps_tombstone_across_a_competing_create() {
6910        let fs = Fs::memory();
6911        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6912        let store = MemoryLazyWorkspaceStore::default();
6913        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store.clone())
6914            .await
6915            .expect("attach");
6916        root.stat("/file.txt").await.expect("observe source");
6917        let prior = root.state().await.expect("state");
6918        let tombstone = root
6919            .insert_overlay(
6920                prior.overlay,
6921                "/file.txt".to_owned(),
6922                LazyOverlayChange::Tombstone,
6923            )
6924            .await
6925            .expect("tombstone");
6926        let pending = LazyWorkspaceState {
6927            revision: prior.revision + 1,
6928            overlay: tombstone,
6929            pending_remove: Some(PendingLazyRemove {
6930                prior_overlay: prior.overlay,
6931                prior_shadows: prior.shadows,
6932                prepared_overlay: tombstone,
6933                kind: PendingLazyRemoveKind::SourceOnly,
6934            }),
6935            ..prior.clone()
6936        };
6937        assert!(
6938            store
6939                .compare_and_swap_lazy_workspace(root.workspace().id(), prior.revision, pending)
6940                .await
6941                .expect("prepare")
6942        );
6943        root.workspace()
6944            .write("/file.txt", Bytes::from_static(b"authored"))
6945            .await
6946            .expect("competing authored create");
6947
6948        let reopened = LazyWorkspace::open(
6949            fs.open_workspace("root").await.expect("workspace"),
6950            Arc::clone(&source),
6951            store,
6952        )
6953        .await
6954        .expect("recover source removal");
6955        assert_eq!(reopened.state().await.expect("state").overlay, tombstone);
6956        assert_eq!(
6957            reopened
6958                .read("/file.txt", 64)
6959                .await
6960                .expect("authored replacement"),
6961            Bytes::from_static(b"authored")
6962        );
6963        reopened.remove("/file.txt").await.expect("remove authored");
6964        assert!(matches!(
6965            reopened.stat("/file.txt").await,
6966            Err(LazyWorkspaceError::NotFound)
6967        ));
6968    }
6969
6970    #[tokio::test]
6971    async fn observation_index_stays_balanced_and_nodes_stay_constant_size() {
6972        let fs = Fs::memory();
6973        let source = Arc::new(CountingSource::new(Bytes::from_static(b"source")));
6974        let store = MemoryLazyWorkspaceStore::default();
6975        let root = LazyWorkspace::attach(&fs, "root", Arc::clone(&source), store.clone())
6976            .await
6977            .expect("attach");
6978        for index in 0..1_000 {
6979            root.stat(&format!("/paths/{index:04}.txt"))
6980                .await
6981                .expect("observe");
6982        }
6983        let root_id = root.state().await.expect("state").overlay;
6984        let state = store
6985            .inner
6986            .lock()
6987            .unwrap_or_else(|poisoned| poisoned.into_inner());
6988        fn depth(overlays: &BTreeMap<LazyOverlayId, LazyOverlay>, id: LazyOverlayId) -> usize {
6989            match &overlays[&id] {
6990                LazyOverlay::Empty => 0,
6991                LazyOverlay::Node { left, right, .. } => {
6992                    1 + depth(overlays, *left).max(depth(overlays, *right))
6993                }
6994            }
6995        }
6996        assert!(depth(&state.overlays, root_id) < 64);
6997        assert!(state.overlays.values().all(|overlay| {
6998            serde_json::to_vec(overlay)
6999                .expect("encode overlay node")
7000                .len()
7001                < 2_048
7002        }));
7003        assert_eq!(source.counts.lookups.load(Ordering::Relaxed), 1_000);
7004    }
7005
7006    #[test]
7007    fn million_path_exactify_capture_memory_is_exactly_admitted() {
7008        const PATHS: u64 = 1_000_000;
7009
7010        fn capture(budget: WorkBudget) -> Result<(WorkCounters, u64), LazyWorkspaceError> {
7011            let mut paths = Vec::new();
7012            let mut retained = 0_u64;
7013            let mut work = WorkCounters::default();
7014            for index in 0..PATHS {
7015                let path = format!("/wide/{index:06}.txt");
7016                reserve_exactify_path_slot(&mut paths, &mut retained, &mut work, budget)?;
7017                retain_exactify_string(&path, &mut retained, &mut work, budget)?;
7018                paths.push(path);
7019            }
7020            let expected = exactify_path_vector_bytes(&paths)?
7021                + paths
7022                    .iter()
7023                    .map(|path| u64::try_from(path.capacity()).unwrap_or(u64::MAX))
7024                    .sum::<u64>();
7025            assert_eq!(retained, expected);
7026            Ok((work, retained))
7027        }
7028
7029        let (work, retained) = capture(WorkBudget::UNBOUNDED).expect("unbounded capture");
7030        assert!(work.peak_allocation_bytes >= retained);
7031        assert!(work.allocation_operations >= PATHS);
7032        let mut insufficient = WorkBudget::UNBOUNDED;
7033        insufficient.peak_allocation_bytes = work.peak_allocation_bytes - 1;
7034        assert!(matches!(
7035            capture(insufficient),
7036            Err(LazyWorkspaceError::Work(_))
7037        ));
7038    }
7039}