     1→import time
     2→from collections import OrderedDict
     3→
     4→from mirage.workspace.cache.entry import CacheEntry
     5→
     6→
     7→def _parse_limit(limit: str | int) -> int:
     8→    if isinstance(limit, int):
     9→        return limit
    10→    s = limit.strip().upper()
    11→    for suffix, mult in [("GB", 1 << 30), ("MB", 1 << 20), ("KB", 1 << 10)]:
    12→        if s.endswith(suffix):
    13→            return int(s[:-len(suffix)]) * mult
    14→    return int(s)
    15→
    16→
    17→class LRUCache:
    18→
    19→    def __init__(self, limit: str | int = "512MB") -> None:
    20→        self._limit = _parse_limit(limit)
    21→        self._store: OrderedDict[str, CacheEntry] = OrderedDict()
    22→        self._size = 0
    23→
    24→    def get(self, path: str) -> CacheEntry | None:
    25→        if path not in self._store:
    26→            return None
    27→        self._store.move_to_end(path)
    28→        return self._store[path]
    29→
    30→    def put(self,
    31→            path: str,
    32→            data: bytes,
    33→            etag: str | None = None,
    34→            modified: str | None = None,
    35→            size: int | None = None) -> None:
    36→        if path in self._store:
    37→            self._size -= len(self._store[path].data)
    38→            del self._store[path]
    39→        self._store[path] = CacheEntry(
    40→            data=data,
    41→            cached_at=time.monotonic(),
    42→            etag=etag,
    43→            modified=modified,
    44→            size=size,
    45→        )
    46→        self._size += len(data)
    47→        self._evict()
    48→
    49→    def invalidate(self, path: str) -> None:
    50→        if path in self._store:
    51→            self._size -= len(self._store[path].data)
    52→            del self._store[path]
    53→
    54→    def clear(self) -> None:
    55→        self._store.clear()
    56→        self._size = 0
    57→
    58→    @property
    59→    def size(self) -> int:
    60→        return self._size
    61→
    62→    @property
    63→    def limit(self) -> int:
    64→        return self._limit
    65→
    66→    def _evict(self) -> None:
    67→        while self._size > self._limit and self._store:
    68→            _, evicted = self._store.popitem(last=False)
    69→            self._size -= len(evicted.data)
    70→


     1→from mirage.workspace.cache.entry import CacheEntry
     2→from mirage.workspace.cache.store import LRUCache
     3→
     4→__all__ = ["CacheEntry", "LRUCache"]
     5→


examples/claude-memory.py:22:from mirage.workspace import Workspace
mirage/workspace/__init__.py:9:from mirage.workspace.cache import LRUCache
mirage/workspace/__init__.py:10:from mirage.workspace.cache.policy import is_fresh
mirage/workspace/__init__.py:11:from mirage.workspace.mount import MountRegistry
mirage/workspace/__init__.py:12:from mirage.workspace.sync import DirtyTracker
examples/claude-sdk.py:26:from mirage.workspace import Workspace
mirage/commands/remote/fastapi/app.py:6:from mirage.workspace import Workspace
mirage/workspace/cache/__init__.py:1:from mirage.workspace.cache.entry import CacheEntry
mirage/workspace/cache/__init__.py:2:from mirage.workspace.cache.store import LRUCache
mirage/workspace/cache/policy.py:5:from mirage.workspace.cache.entry import CacheEntry
mirage/workspace/cache/store.py:4:from mirage.workspace.cache.entry import CacheEntry
mirage/commands/remote/grpc/server.py:8:from mirage.workspace import Workspace
mirage/commands/custom/executor.py:10:from mirage.workspace import Workspace
mirage/commands/builtin/read.py:7:from mirage.workspace import Workspace
mirage/commands/custom/arg_resolver.py:5:from mirage.workspace import Workspace
mirage/commands/custom/registry.py:12:from mirage.workspace import Workspace
mirage/commands/builtin/search.py:7:from mirage.workspace import Workspace
mirage/commands/builtin/handlers.py:5:from mirage.workspace import Workspace
mirage/commands/builtin/write.py:6:from mirage.workspace import Workspace
mirage/commands/builtin/nav.py:6:from mirage.workspace import Workspace
tests/fuse/remote/test_remote.py:6:from mirage.workspace import Workspace
tests/fuse/test_registry.py:65:    from mirage.workspace import Workspace
tests/fuse/test_registry.py:78:    from mirage.workspace import Workspace
tests/fuse/test_registry.py:90:    from mirage.workspace import Workspace
tests/fuse/test_registry.py:102:    from mirage.workspace import Workspace
tests/fuse/test_registry.py:113:    from mirage.workspace import Workspace
tests/conftest.py:5:from mirage.workspace import Workspace
tests/fuse/backend/test_default.py:4:from mirage.workspace import Workspace
tests/fuse/test_fs.py:14:from mirage.workspace import Workspace
tests/fuse/backend/local/test_memory.py:4:from mirage.workspace import Workspace
tests/fuse/filetype/test_parquet.py:9:from mirage.workspace import Workspace
tests/integration/test_remote_transfer.py:7:from mirage.workspace import Workspace
tests/workspace/test_cache_entry.py:3:from mirage.workspace.cache.entry import CacheEntry
tests/commands/builtin/test_read.py:8:from mirage.workspace import Workspace
mirage/observe/inspector.py:11:    from mirage.workspace import Workspace
tests/fuse/filetype/test_csv.py:4:from mirage.workspace import Workspace
tests/workspace/test_init.py:7:from mirage.workspace import Workspace
tests/observe/test_inspector.py:8:from mirage.workspace import Workspace
tests/workspace/test_mount.py:5:from mirage.workspace import Workspace
tests/workspace/test_mount.py:6:from mirage.workspace.mount import MountRegistry
tests/workspace/test_sync.py:4:from mirage.workspace.sync import DirtyTracker
mirage/cli/commands/workspace.py:20:from mirage.workspace import Workspace
tests/commands/test_init.py:4:from mirage.workspace import Workspace
mirage/cli/commands/command.py:47:    from mirage.workspace import Workspace
tests/runtime/test_base.py:6:from mirage.workspace import Workspace
tests/workspace/cache/test_policy.py:2:from mirage.workspace.cache.entry import CacheEntry
tests/workspace/cache/test_policy.py:3:from mirage.workspace.cache.policy import metadata_matches
tests/runtime/test_memory.py:6:from mirage.workspace import Workspace
tests/commands/custom/test_arg_resolver.py:4:from mirage.workspace import Workspace
tests/runtime/accessors/test_local.py:11:from mirage.workspace import Workspace
tests/workspace/cache/test_store.py:1:from mirage.workspace.cache.store import LRUCache
tests/commands/custom/test_registry.py:12:from mirage.workspace import Workspace
mirage/__init__.py:4:from mirage.workspace import Workspace
mirage/runtime/local.py:5:from mirage.workspace import Workspace
mirage/runtime/remote.py:12:from mirage.workspace import Workspace
mirage/fuse/README.md:61:from mirage.workspace import Workspace
mirage/runtime/base.py:14:from mirage.workspace import Workspace
mirage/runtime/memory.py:3:from mirage.workspace import Workspace
mirage/fuse/registry.py:9:    from mirage.workspace import Workspace
mirage/server/service/local.py:27:from mirage.workspace import Workspace
mirage/fuse/fs.py:16:from mirage.workspace import Workspace
mirage/fuse/base.py:6:    from mirage.workspace import Workspace
mirage/fuse/remote/remote.py:9:    from mirage.workspace import Workspace
mirage/runtime/accessors/local.py:7:from mirage.workspace import Workspace
mirage/fuse/filetype/parquet.py:9:    from mirage.workspace import Workspace
mirage/fuse/filetype/csv.py:8:    from mirage.workspace import Workspace


mirage/__init__.py:2:from mirage.types import (ConsistencyPolicy, FileStat, Inode, MountMode,
mirage/__init__.py:10:    "ConsistencyPolicy",
mirage/workspace/mount.py:2:from mirage.types import ConsistencyPolicy, MountMode
mirage/workspace/mount.py:11:                 consistency: ConsistencyPolicy | None = None) -> None:
mirage/workspace/mount.py:35:              consistency: ConsistencyPolicy | None = None) -> None:
mirage/workspace/__init__.py:7:from mirage.types import (ConsistencyPolicy, FileStat, Inode, MountMode,
mirage/workspace/__init__.py:55:        consistency: ConsistencyPolicy = ConsistencyPolicy.NONE,
mirage/workspace/__init__.py:87:              consistency: ConsistencyPolicy | None = None) -> None:
mirage/workspace/__init__.py:335:    def _effective_consistency(self, path: str) -> ConsistencyPolicy:
tests/cli/test_app.py:32:    from mirage.types import ConsistencyPolicy, MountMode, RuntimeType
tests/cli/test_app.py:36:        consistency=ConsistencyPolicy.NONE,
mirage/workspace/cache/policy.py:4:from mirage.types import ConsistencyPolicy, FileStat
mirage/workspace/cache/policy.py:10:    consistency: ConsistencyPolicy,
mirage/workspace/cache/policy.py:15:    if consistency == ConsistencyPolicy.NONE:
mirage/workspace/cache/policy.py:17:    if consistency == ConsistencyPolicy.ALWAYS:
mirage/workspace/cache/policy.py:19:    if consistency == ConsistencyPolicy.TTL:
tests/cli/test_config.py:9:from mirage.types import ConsistencyPolicy, MountMode
tests/cli/test_config.py:126:    assert consistency == ConsistencyPolicy.LAZY
tests/cli/test_config.py:133:    assert consistency == ConsistencyPolicy.NONE
mirage/cli/commands/workspace.py:19:from mirage.types import ConsistencyPolicy, MountMode, RuntimeType
mirage/cli/commands/workspace.py:49:    default_consistency: ConsistencyPolicy,
mirage/cli/commands/workspace.py:52:) -> tuple[dict, MountMode, ConsistencyPolicy, int, str | None]:
mirage/cli/commands/workspace.py:57:            default_consistency = ConsistencyPolicy(cfg["consistency"])
mirage/cli/commands/workspace.py:71:                c = (ConsistencyPolicy(value["consistency"])
mirage/cli/commands/workspace.py:77:                c = (ConsistencyPolicy(value["consistency"])
mirage/cli/commands/workspace.py:107:    consistency: ConsistencyPolicy = typer.Option(ConsistencyPolicy.NONE),
mirage/cli/commands/workspace.py:154:            consistency = ConsistencyPolicy(consistency_str)
mirage/cli/commands/workspace.py:198:    consistency: ConsistencyPolicy,
mirage/cli/commands/command.py:5:from mirage.types import ConsistencyPolicy, MountMode, RuntimeType
mirage/cli/commands/command.py:57:                        value, default_mode, ConsistencyPolicy.NONE)
mirage/cli/commands/command.py:69:                mounts[prefix] = (backend_obj, m, ConsistencyPolicy.NONE)
mirage/cli/commands/command.py:77:                                                 ConsistencyPolicy.NONE)
mirage/cli/commands/command.py:78:        mounts[prefix] = (backend_obj, m, ConsistencyPolicy.NONE)
mirage/cli/config.py:8:from mirage.types import BackendType, ConsistencyPolicy, MountMode
mirage/cli/config.py:106:    default_consistency: ConsistencyPolicy = ConsistencyPolicy.NONE,
mirage/cli/config.py:107:) -> tuple[BaseBackend, MountMode, ConsistencyPolicy]:
mirage/cli/config.py:111:            consistency = ConsistencyPolicy(suffix[1:])
mirage/types.py:49:class ConsistencyPolicy(str, Enum):
mirage/server/service/local.py:25:from mirage.types import (CommandStats, ConsistencyPolicy, FileStat, MountMode,
mirage/server/service/local.py:81:        default_consistency = ConsistencyPolicy(consistency)
mirage/server/service/local.py:97:                mount_consistency = (ConsistencyPolicy(value["consistency"])
tests/workspace/test_init.py:6:from mirage.types import ConsistencyPolicy, MountMode, SyncPolicy
tests/workspace/test_init.py:10:def _make_consistency_ws(consistency=ConsistencyPolicy.NONE, ttl_seconds=300):
tests/workspace/test_init.py:29:    ws, backend = _make_consistency_ws(ConsistencyPolicy.NONE)
tests/workspace/test_init.py:37:    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
tests/workspace/test_init.py:45:    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=300)
tests/workspace/test_init.py:53:    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=10)
tests/workspace/test_init.py:63:    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
tests/workspace/test_init.py:69:    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
tests/workspace/test_init.py:77:    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
tests/workspace/test_init.py:91:            "/b/": (be_always, MountMode.READ, ConsistencyPolicy.ALWAYS),
tests/workspace/test_init.py:94:        consistency=ConsistencyPolicy.NONE,
mirage/server/fastapi/config.py:3:from mirage.types import ConsistencyPolicy, MountMode, SyncPolicy
mirage/server/fastapi/config.py:11:    consistency: ConsistencyPolicy | None = None
mirage/server/fastapi/config.py:18:    consistency: ConsistencyPolicy = ConsistencyPolicy.NONE


     1→from mirage.backend.local import LocalBackend, MemoryBackend
     2→from mirage.types import (ConsistencyPolicy, FileStat, Inode, MountMode,
     3→                          SyncPolicy, SyncResult)
     4→from mirage.workspace import Workspace
     5→
     6→__all__ = [
     7→    "Workspace",
     8→    "MemoryBackend",
     9→    "LocalBackend",
    10→    "ConsistencyPolicy",
    11→    "FileStat",
    12→    "Inode",
    13→    "MountMode",
    14→    "SyncPolicy",
    15→    "SyncResult",
    16→]
    17→


     1→import os
     2→import shlex
     3→import uuid
     4→
     5→from mirage.commands import _resolve
     6→from mirage.commands.builtin import nav
     7→from mirage.commands.custom.registry import CommandRegistry
     8→from mirage.fuse.registry import FuseOpRegistry
     9→from mirage.runtime.accessors.base import (CommandsAccessor, FuseOpsAccessor,
    10→                                           WorkspaceOpsAccessor)
    11→from mirage.runtime.accessors.local import (LocalCommandsAccessor,
    12→                                            LocalFuseOpsAccessor,
    13→                                            LocalWorkspaceOpsAccessor)
    14→from mirage.workspace import Workspace
    15→
    16→_ENV_AGENT_ID = "MIRAGE_AGENT_ID"
    17→
    18→_BLOCKED = {
    19→    "python",
    20→    "python3",
    21→    "node",
    22→    "npm",
    23→    "npx",
    24→    "ruby",
    25→    "perl",
    26→    "php",
    27→    "bash",
    28→    "sh",
    29→    "zsh",
    30→    "fish",
    31→    "exec",
    32→    "eval",
    33→    "source",
    34→    "docker",
    35→    "kubectl",
    36→    "terraform",
    37→    "ansible",
    38→    "git",
    39→    "svn",
    40→    "ssh",
    41→    "scp",
    42→    "sftp",
    43→    "rsync",
    44→    "sudo",
    45→    "su",
    46→    "chmod",
    47→    "chown",
    48→    "kill",
    49→    "ps",
    50→}
    51→
    52→
    53→class CommandNotAllowedError(Exception):
    54→    pass
    55→
    56→
    57→class Runtime:
    58→
    59→    def __init__(self,
    60→                 workspace: Workspace,
    61→                 registry: CommandRegistry | None = None,
    62→                 agent_id: str | None = None) -> None:
    63→        self.ws = workspace
    64→        self._registry = registry or CommandRegistry()
    65→        self._cwd = "/"
    66→        self.agent_id = (agent_id or os.environ.get(_ENV_AGENT_ID)
    67→                         or f"agent-{uuid.uuid4().hex[:8]}")
    68→        self.commands: CommandsAccessor = LocalCommandsAccessor(self._registry)
    69→        self.fuse_ops: FuseOpsAccessor = LocalFuseOpsAccessor(FuseOpRegistry())
    70→        self.workspace: WorkspaceOpsAccessor = LocalWorkspaceOpsAccessor(
    71→            workspace)
    72→
    73→    def run(self, command: str) -> str:
    74→        parts = shlex.split(command)
    75→        if not parts:
    76→            return ""
    77→        cmd, argv = parts[0], parts[1:]
    78→        if cmd in _BLOCKED:
    79→            raise CommandNotAllowedError(f"{cmd!r} is not allowed in Runtime.")
    80→        if cmd == "cd":
    81→            if not argv:
    82→                self._cwd = "/"
    83→                return ""
    84→            self._cwd = nav.cd(self.ws, self._cwd,
    85→                               _resolve(self._cwd, argv[0]))
    86→            return ""
    87→        if cmd == "whoami":
    88→            return self._whoami()
    89→        if cmd == "identify":
    90→            if argv:
    91→                self.agent_id = " ".join(argv)
    92→            return self.agent_id or ""
    93→        return self._registry.run(self.ws, command, self._cwd)
    94→
    95→    def _whoami(self) -> str:
    96→        lines = [f"agent: {self.agent_id}", f"cwd: {self._cwd}"]
    97→        try:
    98→            mounts = list(self.ws._registry._mounts.keys())
    99→            lines.append(f"mounts: {', '.join(mounts)}")
   100→        except Exception:
   101→            pass
   102→        return "\n".join(lines)
   103→


     1→from __future__ import annotations
     2→
     3→import fnmatch
     4→from typing import TYPE_CHECKING
     5→
     6→from mirage.fuse import FUSE_OPS, FuseOp
     7→
     8→if TYPE_CHECKING:
     9→    from mirage.workspace import Workspace
    10→
    11→_BACKEND_OP_MAP: dict[str, str] = {
    12→    "S3Backend": "s3",
    13→    "R2Backend": "s3",
    14→    "MemoryBackend": "memory",
    15→    "LocalBackend": "local",
    16→}
    17→
    18→
    19→class FuseOpRegistry:
    20→
    21→    def __init__(self) -> None:
    22→        self._ops: list[tuple[str, FuseOp]] = []
    23→        self._default: FuseOp = FUSE_OPS["default"]
    24→
    25→    def register(self, pattern: str, op: FuseOp) -> None:
    26→        self._ops.append((pattern, op))
    27→
    28→    def set_default(self, op: FuseOp) -> None:
    29→        self._default = op
    30→
    31→    def resolve(self,
    32→                path: str,
    33→                size: int,
    34→                ws: Workspace | None = None) -> FuseOp:
    35→        basename = path.rsplit("/", 1)[-1] if "/" in path else path
    36→        for pattern, op in self._ops:
    37→            if fnmatch.fnmatch(basename, pattern):
    38→                return op
    39→        if ws is not None:
    40→            try:
    41→                backend, _, _ = ws._registry.resolve(path)
    42→                backend_name = type(backend).__name__
    43→                op_key = _BACKEND_OP_MAP.get(backend_name)
    44→                if op_key and op_key in FUSE_OPS:
    45→                    return FUSE_OPS[op_key]
    46→            except (ValueError, FileNotFoundError):
    47→                pass
    48→        return self._default
    49→
    50→    def list_ops(self) -> list[dict]:
    51→        ops = [{"pattern": p, "name": o.name} for p, o in self._ops]
    52→        ops.append({"pattern": "*", "name": self._default.name})
    53→        return ops
    54→


Conversation compacted


This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.

Analysis:
Let me chronologically analyze the conversation:

1. **Context from previous session**: The conversation starts with a continuation summary describing extensive refactoring work already done:
   - Backend refactoring (grouping into subfolders)
   - Extracting `_guess_type` into `filetype.py`
   - Renaming `backends` to `backend`
   - Using FileType enum
   - Refactoring fuse_ops into fuse
   - Merging separate fuse package into mirage/fuse
   - Grouping fuse backends into object_storage, local subfolders

2. **Test directory reorganization (plan mode)**: The session continued with planning the test reorganization. The user had already requested: "Ok let's refactor the tests by 1. no class just have def test_xxx 2. each source file should have exactly one test file for example backend/abc/def.py should have tests/backend/abc/test_def.py"

3. **Writing the plan**: I explored the test and source structures, then wrote a comprehensive plan to `/Users/agent/.claude/plans/shimmering-leaping-wozniak.md` covering moves, splits, merges, and new directories.

4. **Executing the test reorganization**:
   - Created 15 new directories
   - Executed 15 git mv moves
   - Executed 3 renames
   - Split test_configs.py → test_s3.py, test_r2.py (ExternalCommand tests merged into commands/custom/test_registry.py)
   - Split test_default.py → fuse/backend/test_default.py + fuse/backend/local/test_memory.py
   - Split test_format_ops.py → fuse/filetype/test_parquet.py + fuse/filetype/test_csv.py
   - Split server/test_registry.py → commands/custom/test_registry.py + server/test_constants.py
   - Merged tests/test_types.py + tests/types/test_types.py → tests/test_types.py
   - Merged workspace test files (test_write_ops.py + test_lock.py + test_init.py) → test_init.py

5. **User interrupted**: "wait why init in tests remove them" — user didn't want `__init__.py` files in test directories.

6. **Removing __init__.py from tests**: Removed all existing `__init__.py` files from test directories. This caused import collisions (duplicate test file names like `test_local.py` in multiple directories). Fixed by adding `--import-mode=importlib` to pytest config in `pyproject.toml`.

7. **All 641 tests passed** after the test reorganization.

8. **User's next request**: "why we have workspace init containing a lot of functions and classes? refactor init only contains import etc and also the test need to be changed also why ConsistencyPolicy can be None? what's the default ConsistencyPolicy?"

9. **Planning the workspace refactoring**: Entered plan mode, explored workspace module thoroughly.

10. **User decisions via AskUserQuestion**:
    - Rename `ConsistencyPolicy.NONE` → `ConsistencyPolicy.CACHED`
    - Every mount must have explicit ConsistencyPolicy (no None = inherit)

11. **Implementing workspace refactoring**:
    - Renamed `ConsistencyPolicy.NONE` → `CACHED` in types.py and all 14 references across codebase
    - Updated `_CONSISTENCY_SUFFIXES` from `#none` to `#cached` in config.py and workspace.py
    - Updated help text in CLI
    - Made `Mount.consistency` always explicit: `ConsistencyPolicy = ConsistencyPolicy.CACHED` (was `ConsistencyPolicy | None = None`)
    - Removed `_effective_consistency()` method from Workspace
    - Removed `self._consistency` instance variable (no longer needed)
    - Changed tuple parsing in Workspace.__init__ to pass `consistency` instead of `None`
    - Moved Workspace class + _PathLock to `mirage/workspace/workspace.py`
    - Rewrote `mirage/workspace/__init__.py` as just re-exports
    - Renamed `tests/workspace/test_init.py` → `tests/workspace/test_workspace.py`
    - Updated `server/service/local.py`: default string `"none"` → `"cached"`
    - Updated `server/fastapi/config.py`: `MountConfig.consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED`
    - Updated `server/service/local.py`: Simplified mount consistency logic (no more None checks)

12. **Errors encountered**:
    - After removing `__init__.py` from tests: pytest import collision errors for duplicate test file names → Fixed with `--import-mode=importlib`
    - After renaming `ConsistencyPolicy.NONE` → `CACHED`: 21 tests failed with `'none' is not a valid ConsistencyPolicy` because `server/service/local.py` had a hardcoded default string `"none"` → Fixed by changing to `"cached"`

13. **Final state**: All 641 tests pass, all pre-commit checks pass.

Now let me capture all the important files and their current states.

Summary:
1. Primary Request and Intent:
   The user has been conducting a series of large-scale refactorings on the `mirage` project across multiple sessions:

   **Previous sessions (already completed):**
   - Group similar backends into subfolders (object_storage, local, network) within `mirage/backend/`
   - Extract `_guess_type` into `filetype.py` with extensible `EXTENSION_MAP` and binary default
   - Rename `backends/` → `backend/` across entire codebase
   - Use `FileType` enum instead of raw strings, tighten `FileStat.type` to `FileType | None`
   - Refactor fuse_ops → fuse, merge separate fuse package into `mirage/fuse/`, group by backend/filetype/remote
   - Group fuse backends similarly to main backends (local/, object_storage/)

   **This session:**
   - Refactor tests: No classes, just `def test_xxx`. Each source file `mirage/a/b/c.py` should have exactly one test file `tests/a/b/test_c.py`
   - User explicitly requested removing all `__init__.py` from test directories
   - Refactor `workspace/__init__.py`: Move Workspace class to its own file, `__init__.py` should only contain imports/re-exports
   - Rename `ConsistencyPolicy.NONE` → `ConsistencyPolicy.CACHED` (user chose "CACHED" from options)
   - Make `Mount.consistency` always explicit — every mount must specify its own `ConsistencyPolicy`, no `None` inheritance (user chose "Require explicit")

2. Key Technical Concepts:
   - Python package reorganization with `git mv` for proper tracking
   - pytest `--import-mode=importlib` to support duplicate test file names without `__init__.py`
   - `ConsistencyPolicy(str, Enum)` for cache validation strategies
   - Per-mount vs workspace-level consistency policies
   - `MountRegistry` longest-prefix-match routing
   - FUSE filesystem ops architecture
   - Pydantic models for API request/response
   - Pre-commit hooks (yapf, ruff, isort, etc.)

3. Files and Code Sections:

   - **`mirage/types.py`** — Enum rename
     - Changed `ConsistencyPolicy.NONE = "none"` → `ConsistencyPolicy.CACHED = "cached"`
     ```python
     class ConsistencyPolicy(str, Enum):
         CACHED = "cached"
         LAZY = "lazy"
         TTL = "ttl"
         ALWAYS = "always"
     ```

   - **`mirage/workspace/workspace.py`** (NEW) — Workspace class moved here from `__init__.py`
     - Contains `_PathLock` class and `Workspace` class (~340 lines)
     - `_effective_consistency()` method removed — mounts always carry explicit policy
     - `self._consistency` instance variable removed
     - Constructor passes workspace-level `consistency` to mounts that don't specify their own via tuple
     ```python
     class Workspace:
         def __init__(
             self,
             backends: dict[str, BaseBackend | tuple],
             cache_limit: str | int = "512MB",
             sync_policy: SyncPolicy = SyncPolicy.STAGED,
             mode: MountMode = MountMode.READ,
             consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
             ttl_seconds: int = 300,
         ) -> None:
             # ...
             for prefix, value in backends.items():
                 if isinstance(value, tuple) and len(value) == 3:
                     backend, mount_mode, mount_consistency = value
                 elif isinstance(value, tuple):
                     backend, mount_mode = value
                     mount_consistency = consistency  # was None
                 else:
                     backend = value
                     mount_mode = mode
                     mount_consistency = consistency  # was None
                 self._registry.mount(prefix, backend, mount_mode, mount_consistency)
     ```
     - In `cat()`: `consistency = self._registry.mount_for(path).consistency` (was `self._effective_consistency(path)`)

   - **`mirage/workspace/__init__.py`** — Rewritten as re-exports only
     ```python
     from mirage.workspace.workspace import Workspace

     __all__ = ["Workspace"]
     ```

   - **`mirage/workspace/mount.py`** — Mount.consistency always explicit
     ```python
     class Mount:
         def __init__(
                 self,
                 prefix: str,
                 backend: BaseBackend,
                 mode: MountMode = MountMode.READ,
                 consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
     ```
     ```python
     class MountRegistry:
         def mount(
                 self,
                 prefix: str,
                 backend: BaseBackend,
                 mode: MountMode = MountMode.READ,
                 consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
     ```

   - **`mirage/workspace/cache/policy.py`** — Updated enum reference
     ```python
     if consistency == ConsistencyPolicy.CACHED:
         return True, None
     ```

   - **`mirage/cli/config.py`** — Updated suffixes and default
     ```python
     _CONSISTENCY_SUFFIXES = ("#cached", "#lazy", "#ttl", "#always")

     def parse_mount_uri_full(
         uri: str,
         default_mode: MountMode = MountMode.READ,
         default_consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
     ) -> tuple[BaseBackend, MountMode, ConsistencyPolicy]:
     ```

   - **`mirage/cli/commands/workspace.py`** — Updated suffixes, help text, defaults
     - `consistency_suffixes = ("#cached", "#lazy", "#ttl", "#always")`
     - Help text: `"Append #cached|#lazy|#ttl|#always for consistency"`
     - `consistency: ConsistencyPolicy = typer.Option(ConsistencyPolicy.CACHED)`

   - **`mirage/cli/commands/command.py`** — All 4 `ConsistencyPolicy.NONE` → `ConsistencyPolicy.CACHED`

   - **`mirage/server/fastapi/config.py`** — MountConfig consistency always explicit
     ```python
     class MountConfig(BaseModel):
         uri: str | None = None
         backend: str | None = None
         args: dict[str, str] | None = None
         mode: MountMode | None = None
         consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED

     class CreateWorkspaceRequest(BaseModel):
         consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED
     ```

   - **`mirage/server/service/local.py`** — Updated default string and simplified logic
     ```python
     consistency = config.get("consistency", "cached")  # was "none"
     ```
     Simplified mount consistency handling:
     ```python
     mount_consistency = (ConsistencyPolicy(value["consistency"])
                          if value.get("consistency") else
                          default_consistency)
     if (mount_mode != default_mode
             or mount_consistency != default_consistency):
         backends[prefix] = (backend, mount_mode, mount_consistency)
     else:
         backends[prefix] = backend
     ```

   - **`pyproject.toml`** — Added importlib import mode for pytest
     ```toml
     [tool.pytest.ini_options]
     testpaths = ["tests"]
     addopts = "--cov=mirage --cov-report=term-missing -q --import-mode=importlib"
     ```

   - **Test reorganization** — 15 moves, 3 renames, 4 splits, 2 merges:
     - All `__init__.py` removed from test directories
     - `tests/workspace/test_workspace.py` (renamed from test_init.py, merged from test_init.py + test_write_ops.py + test_lock.py)
     - `tests/test_types.py` (merged from tests/test_types.py + tests/types/test_types.py)
     - `tests/backend/local/test_memory.py`, `tests/backend/local/test_local.py` (moved from tests/backend/)
     - `tests/backend/network/test_proxy.py` (moved)
     - `tests/backend/object_storage/test_s3.py`, `test_r2.py` (split from test_configs.py)
     - `tests/commands/builtin/test_handlers.py`, `test_nav.py`, `test_read.py`, `test_search.py`, `test_write.py` (moved)
     - `tests/commands/remote/test_client.py` (moved)
     - `tests/commands/custom/test_registry.py` (split from server/test_registry.py + test_configs.py ExternalCommand tests)
     - `tests/commands/custom/test_arg_resolver.py` (moved from server/)
     - `tests/fuse/backend/test_default.py`, `tests/fuse/backend/local/test_memory.py` (split from test_default.py)
     - `tests/fuse/filetype/test_parquet.py`, `test_csv.py` (split from test_format_ops.py)
     - `tests/fuse/remote/test_remote.py` (moved)
     - `tests/server/fastapi/test_app.py`, `conftest.py` (moved)
     - `tests/server/fastapi/routers/test_io_router.py` (moved)
     - `tests/server/service/test_cache.py` (moved from test_file_cache.py)
     - `tests/server/service/test_local.py` (renamed from test_service.py)
     - `tests/server/test_constants.py` (split from test_registry.py, contains test_parse_size_units)
     - `tests/runtime/test_base.py` (renamed from test_runtime.py)
     - `tests/runtime/accessors/test_local.py` (renamed from test_accessors.py)
     - `tests/observe/fastapi/test_router.py` — NOT moved (test_api.py stayed; this was in the plan but the explore agent noted it tests the router)

4. Errors and fixes:
   - **pytest import collision after removing `__init__.py`**: After removing all `__init__.py` from test directories, pytest couldn't distinguish test modules with the same name (e.g., `test_local.py` in `tests/backend/local/`, `tests/server/service/`, `tests/runtime/`). Error: `import file mismatch: imported module 'test_local' has this __file__ attribute: /path/to/tests/backend/local/test_local.py which is not the same as the test file we want to collect: /path/to/tests/server/service/test_local.py`. Fixed by adding `--import-mode=importlib` to pytest addopts in `pyproject.toml`.

   - **`'none' is not a valid ConsistencyPolicy`**: After renaming `ConsistencyPolicy.NONE` to `CACHED`, 21 tests in `tests/server/service/test_local.py` failed because `mirage/server/service/local.py` line 79 had `config.get("consistency", "none")` — a hardcoded default string `"none"` that was being passed to `ConsistencyPolicy()`. Fixed by changing to `config.get("consistency", "cached")`.

   - **User feedback on `__init__.py`**: User explicitly interrupted with "wait why init in tests remove them" — they did NOT want `__init__.py` files created in test directories. The original plan included creating 15 `__init__.py` files; this was changed to not create them and instead remove existing ones.

5. Problem Solving:
   - Test reorganization completed successfully with all 641 tests passing
   - Workspace refactoring completed successfully with all 641 tests passing
   - All pre-commit checks pass (only `no-commit-to-branch` fails which is expected on main branch)
   - Used `git mv` for moves/renames to preserve git history
   - Used `--import-mode=importlib` to handle duplicate test file names without `__init__.py`
   - Handled the cascade of `ConsistencyPolicy` rename across source code, CLI strings, help text, and test expectations

6. All user messages:
   - (Session continuation from previous context with test reorganization plan in progress)
   - "wait why init in tests remove them" (interrupted the __init__.py creation)
   - "why we have workspace init containing a lot of functions and classes? refactor init only contains import etc and also the test need to be changed also why ConsistencyPolicy can be None? what's the default ConsistencyPolicy?"
   - AskUserQuestion response: chose "CACHED" for renaming ConsistencyPolicy.NONE, chose "Require explicit" for per-mount consistency

7. Pending Tasks:
   - No explicitly pending tasks. Both the test reorganization and workspace refactoring are complete.

8. Current Work:
   The most recent work completed was the workspace `__init__.py` refactoring combined with `ConsistencyPolicy` cleanup. All changes have been verified:
   - 641 tests pass
   - All pre-commit hooks pass
   - `ConsistencyPolicy.NONE` renamed to `ConsistencyPolicy.CACHED` everywhere
   - `Mount.consistency` is always explicit (never `None`)
   - `Workspace` class moved from `workspace/__init__.py` to `workspace/workspace.py`
   - `workspace/__init__.py` now only contains re-exports
   - Test file renamed from `test_init.py` to `test_workspace.py`

   Current directory structures:
   ```
   mirage/backend/
   ├── __init__.py, base.py, filetype.py, loader.py
   ├── local/ (local.py, memory.py)
   ├── network/ (http.py, proxy.py)
   └── object_storage/ (r2.py, s3.py)

   mirage/fuse/
   ├── __init__.py, base.py, fs.py, loader.py, registry.py, README.md
   ├── backend/ (default.py, local/{local.py,memory.py}, object_storage/s3.py)
   ├── filetype/ (csv.py, parquet.py)
   └── remote/ (remote.py)

   mirage/workspace/
   ├── __init__.py (re-exports only)
   ├── workspace.py (Workspace class + _PathLock)
   ├── mount.py, sync.py
   └── cache/ (entry.py, policy.py, store.py)
   ```

9. Optional Next Step:
   No pending tasks. The user's most recent requests have all been completed and verified. No changes have been committed yet — the user may want to commit or continue with further refactoring.

If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/agent/.claude/projects/-Users-agent-strukto-mirage/664396f7-55a4-4604-8289-aa7142480db4.jsonl


<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<command-name>/compact</command-name>
            <command-message>compact</command-message>
            <command-args></command-args>


<local-command-stdout>Compacted </local-command-stdout>


Ok now give me how the consistency is used for the workspace and runtime? I am not sure for example how consistency is used for workspace deployed at remote?


The user wants to understand how `ConsistencyPolicy` is used across the workspace and runtime, particularly in remote deployment scenarios. Let me explore the codebase to trace how consistency flows through the system.


Let me trace how `ConsistencyPolicy` flows through the system.


Agent

Trace ConsistencyPolicy usage

Prompt:
I need to understand how ConsistencyPolicy is used throughout the mirage codebase. Trace all usages comprehensively:

1. Find the enum definition in mirage/types.py
2. Find ALL files that reference ConsistencyPolicy
3. Read the key files that USE consistency (not just pass it through), especially:
   - mirage/workspace/cache/policy.py (is_fresh function)
   - mirage/workspace/workspace.py (how cat/read use it)
   - mirage/server/service/local.py (how remote server uses it)
   - mirage/server/fastapi/ routers (how API endpoints handle it)
   - mirage/runtime/ (any runtime usage)

For each file, explain HOW consistency is actually used (not just imported/passed). I want to understand the data flow from API request → workspace → cache → backend.


Perfect! Now I have a complete picture. Let me create the comprehensive trace report:

## ConsistencyPolicy Data Flow Trace

I've traced the ConsistencyPolicy enum throughout the mirage codebase. Here's a comprehensive analysis of how it flows from API requests through to actual cache usage:

---

## 1. ENUM DEFINITION

**File:** `/Users/agent/strukto/mirage/mirage/types.py` (lines 49-53)

```python
class ConsistencyPolicy(str, Enum):
    CACHED = "cached"
    LAZY = "lazy"
    TTL = "ttl"
    ALWAYS = "always"
```

The enum has **4 policies** that control cache freshness validation behavior.

---

## 2. WHERE CONSISTENCYPOLICY IS USED (10 Files)

1. **`mirage/types.py`** - Definition
2. **`mirage/__init__.py`** - Public export
3. **`mirage/server/fastapi/config.py`** - API request models
4. **`mirage/server/service/local.py`** - Service layer (workspace creation)
5. **`mirage/workspace/cache/policy.py`** - **Cache freshness logic** (KEY FILE)
6. **`mirage/workspace/workspace.py`** - **Workspace mounting & read ops** (KEY FILE)
7. **`mirage/workspace/mount.py`** - Mount point storage
8. **`mirage/cli/config.py`** - CLI URI parsing with consistency suffixes
9. **`mirage/cli/commands/workspace.py`** - CLI mount command
10. **`mirage/cli/commands/command.py`** - Remote command server

---

## 3. DATA FLOW: API REQUEST → WORKSPACE → CACHE

### Step 1: API Request Layer (FastAPI)

**File:** `/Users/agent/strukto/mirage/mirage/server/fastapi/config.py`

```python
class MountConfig(BaseModel):
    uri: str | None = None
    backend: str | None = None
    args: dict[str, str] | None = None
    mode: MountMode | None = None
    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED  # Default: CACHED

class CreateWorkspaceRequest(BaseModel):
    mounts: dict[str, str | MountConfig]
    sync_policy: SyncPolicy = SyncPolicy.STAGED
    mode: MountMode = MountMode.WRITE
    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED  # Global default
    ttl_seconds: int = 300
    runtime: str | None = None
    fuse_ops: dict[str, str] | None = None
```

The API accepts a **global `consistency` parameter** and per-mount overrides in `MountConfig`.

**Router:** `/Users/agent/strukto/mirage/mirage/server/fastapi/routers/workspace_router.py`

```python
@workspace_router.post(routes.WORKSPACES)
def create_workspace(req: CreateWorkspaceRequest, request: Request) -> dict:
    svc = _get_service(request)
    ws_id = svc.create_workspace(req.model_dump())  # Passes entire config dict
    return {"id": ws_id}
```

The router simply passes the request body as a dict to the service.

---

### Step 2: Service Layer (LocalService)

**File:** `/Users/agent/strukto/mirage/mirage/server/service/local.py` (lines 77-112)

```python
def create_workspace(self, config: dict) -> str:
    mode = config.get("mode", "write")
    consistency = config.get("consistency", "cached")
    default_mode = MountMode(mode)
    default_consistency = ConsistencyPolicy(consistency)  # Convert string to enum
    mounts = config.get("mounts", {})

    backends: dict = {}
    for prefix, value in mounts.items():
        if isinstance(value, str):
            backends[prefix] = parse_mount_uri(value)
        else:
            backend_spec = value.get("backend")
            if backend_spec:
                cls = load_backend_class(backend_spec)
                backend = cls(**(value.get("args") or {}))
            else:
                backend = parse_mount_uri(value.get("uri") or "")

            mount_mode = (MountMode(value["mode"])
                          if value.get("mode") else default_mode)
            mount_consistency = (ConsistencyPolicy(value["consistency"])
                                 if value.get("consistency") else
                                 default_consistency)  # Per-mount override

            if (mount_mode != default_mode
                    or mount_consistency != default_consistency):
                backends[prefix] = (backend, mount_mode, mount_consistency)
            else:
                backends[prefix] = backend

    ws = Workspace(
        backends,
        sync_policy=SyncPolicy(config.get("sync_policy", "staged")),
        mode=default_mode,
        consistency=default_consistency,
        ttl_seconds=config.get("ttl_seconds", 300),
    )
```

**Key Logic:**
- Converts string consistency value to `ConsistencyPolicy` enum
- Applies global default or per-mount overrides
- Stores both in tuple form: `(backend, mode, consistency)`
- Passes to Workspace constructor

---

### Step 3: Mount Registry

**File:** `/Users/agent/strukto/mirage/mirage/workspace/mount.py`

```python
class Mount:
    def __init__(
            self,
            prefix: str,
            backend: BaseBackend,
            mode: MountMode = MountMode.READ,
            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
        stripped = prefix.strip("/")
        self.prefix = "/" + stripped + "/" if stripped else "/"
        self.backend = backend
        self.mode = mode
        self.consistency = consistency  # Stored on Mount object
```

Each mount stores its `consistency` policy as an instance attribute.

**File:** `/Users/agent/strukto/mirage/mirage/workspace/mount.py` (lines 32-43)

```python
class MountRegistry:
    def mount(
            self,
            prefix: str,
            backend: BaseBackend,
            mode: MountMode = MountMode.READ,
            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
        m = Mount(prefix, backend, mode, consistency)
        for existing in self._mounts:
            if existing.prefix == m.prefix:
                raise ValueError(f"duplicate mount prefix: {m.prefix!r}")
        self._mounts.append(m)
        self._mounts.sort(key=lambda x: len(x.prefix), reverse=True)
```

The registry stores Mount objects and sorts by prefix length (longest-prefix match).

---

### Step 4: Workspace Initialization & Read Operations

**File:** `/Users/agent/strukto/mirage/mirage/workspace/workspace.py` (lines 49-78)

```python
class Workspace:
    def __init__(
        self,
        backends: dict[str, BaseBackend | tuple],
        cache_limit: str | int = "512MB",
        sync_policy: SyncPolicy = SyncPolicy.STAGED,
        mode: MountMode = MountMode.READ,
        consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
        ttl_seconds: int = 300,
    ) -> None:
        self._registry = MountRegistry()
        self._cache = LRUCache(cache_limit)
        self._tracker = DirtyTracker()
        self._sync_policy = sync_policy
        self._ttl_seconds = ttl_seconds  # Stored for TTL policy checks
        self._locked_paths: set[str] = set()
        self._transfer_stats: dict[str, TransferStats] = {}
        self._created_at: float = time.monotonic()

        for prefix, value in backends.items():
            if isinstance(value, tuple) and len(value) == 3:
                backend, mount_mode, mount_consistency = value
            elif isinstance(value, tuple):
                backend, mount_mode = value
                mount_consistency = consistency
            else:
                backend = value
                mount_mode = mode
                mount_consistency = consistency
            self._registry.mount(prefix, backend, mount_mode,
                                 mount_consistency)
```

Workspace stores:
- `_ttl_seconds` - Used by TTL policy to check cache age
- Mounts consistency via registry (longest-prefix match)

---

### Step 5: The Critical Path - Cat/Read Operations with Consistency Check

**File:** `/Users/agent/strukto/mirage/mirage/workspace/workspace.py` (lines 95-140)

```python
def cat(self, path: str) -> bytes:
    inode = self._tracker.get(path)
    if inode and inode.dirty:
        entry = self._cache.get(path)
        if entry is not None:
            self._record_transfer(path, "read", len(entry.data), 0, cache_hit=True)
            return entry.data

    entry = self._cache.get(path)
    remote_stat = None
    if entry is not None:
        # CONSISTENCY CHECK HAPPENS HERE
        consistency = self._registry.mount_for(path).consistency  # Get from mount
        backend, bp, _ = self._registry.resolve(path)
        fresh, remote_stat = is_fresh(entry, consistency,
                                      self._ttl_seconds, backend, bp)
        if fresh:
            self._record_transfer(path, "read", len(entry.data), 0, cache_hit=True)
            return entry.data
    else:
        backend, bp, _ = self._registry.resolve(path)

    # Cache miss or stale - fetch from backend
    t0 = time.monotonic()
    data = backend.cat(bp)
    duration_ms = int((time.monotonic() - t0) * 1000)
    self._record_transfer(path, "read", len(data), duration_ms, cache_hit=False)
    if remote_stat is None:
        remote_stat = backend.stat(bp)
    self._cache.put(path, data,
                    etag=remote_stat.extra.get("etag"),
                    modified=remote_stat.modified,
                    size=remote_stat.size)
    self._tracker.on_read(path, data, self._mount_prefix(path))
    return data
```

**Data Flow:**
1. Check if file is cached (`entry = self._cache.get(path)`)
2. If cached, get **consistency policy from mount registry**: `consistency = self._registry.mount_for(path).consistency`
3. Call `is_fresh()` to check if cache is still valid
4. If fresh, return cached data (cache hit)
5. If stale or missing, fetch from backend and update cache

---

### Step 6: THE CORE LOGIC - Cache Freshness Validation

**File:** `/Users/agent/strukto/mirage/mirage/workspace/cache/policy.py` (lines 8-38)

This is where consistency policies are **actually enforced**:

```python
def is_fresh(
    entry: CacheEntry,
    consistency: ConsistencyPolicy,
    ttl_seconds: int,
    backend: BaseBackend,
    backend_path: str,
) -> tuple[bool, FileStat | None]:
    # CACHED: Always return cached data without checking backend
    if consistency == ConsistencyPolicy.CACHED:
        return True, None

    # ALWAYS: Never trust cache, always check backend
    if consistency == ConsistencyPolicy.ALWAYS:
        return False, None

    # TTL: Check if entry age < ttl_seconds, if so trust cache
    if consistency == ConsistencyPolicy.TTL:
        if (time.monotonic() - entry.cached_at) < ttl_seconds:
            return True, None

    # LAZY: Fall through to metadata matching
    # (both TTL timeout and LAZY check backend metadata)
    try:
        remote = backend.stat(backend_path)
    except FileNotFoundError:
        return False, None
    return metadata_matches(entry, remote), remote

def metadata_matches(entry: CacheEntry, remote: FileStat) -> bool:
    # Compares cached metadata with backend metadata
    remote_etag = remote.extra.get("etag")
    if entry.etag and remote_etag:
        return entry.etag == remote_etag
    if entry.modified and remote.modified:
        return entry.modified == remote.modified
    if entry.size is not None and remote.size is not None:
        return entry.size == remote.size
    return False
```

**Cache Entry Structure** (`/Users/agent/strukto/mirage/mirage/workspace/cache/entry.py`):
```python
class CacheEntry(BaseModel):
    data: bytes
    cached_at: float          # Timestamp for TTL calculation
    etag: str | None = None   # For LAZY metadata matching
    modified: str | None = None  # For LAZY metadata matching
    size: int | None = None   # For LAZY metadata matching
```

---

## 4. CONSISTENCY POLICY BEHAVIORS BREAKDOWN

| Policy | Behavior | Use Case |
|--------|----------|----------|
| **CACHED** | Always return cached data without checking backend | Fast reads, data never changes or changes don't matter |
| **ALWAYS** | Always fetch from backend, never use cache | Must have latest data, consistency critical |
| **TTL** | Use cache if `time.monotonic() - cached_at < ttl_seconds`, else check backend | Data changes infrequently, acceptable staleness window |
| **LAZY** | Always check backend metadata (etag/mtime/size) against cache | Balance: use cache if unchanged, fetch if modified |

---

## 5. CLI INTERFACE

**File:** `/Users/agent/strukto/mirage/mirage/cli/config.py` (lines 100-115)

The CLI supports consistency suffixes on mount URIs:

```python
_CONSISTENCY_SUFFIXES = ("#cached", "#lazy", "#ttl", "#always")

def parse_mount_uri_full(
    uri: str,
    default_mode: MountMode = MountMode.READ,
    default_consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
) -> tuple[BaseBackend, MountMode, ConsistencyPolicy]:
    consistency = default_consistency
    for suffix in _CONSISTENCY_SUFFIXES:
        if uri.endswith(suffix):
            consistency = ConsistencyPolicy(suffix[1:])
            uri = uri[:-len(suffix)]
            break
    backend, mode = parse_mount_uri_with_mode(uri, default_mode)
    return backend, mode, consistency
```

**Example CLI Usage:**
```bash
mirage workspace mount s3://bucket/data#ttl:/data@read s3://bucket/logs#always:/logs@write
```

- `#ttl` suffix sets TTL consistency
- `#always` suffix enforces always-check policy
- `@read` suffix controls mount mode
- Without suffix, defaults to `#cached`

---

## 6. KEY INSIGHTS

1. **Consistency is per-mount**: Each mount can have its own policy, controlled at workspace creation time
2. **Default is CACHED**: All mount points default to CACHED unless overridden
3. **TTL requires timing**: TTL policy uses monotonic time stored in cache entries
4. **Lazy validation**: LAZY policy must call `backend.stat()` every read to check metadata
5. **Cache bypassed entirely for ALWAYS**: ALWAYS policy skips cache layer completely
6. **Metadata-based freshness**: LAZY/TTL-timeout use etag/mtime/size comparison
7. **No consistency in write path**: Write operations don't check consistency policies
8. **Transfer stats tracked**: Every read records cache hits/misses for monitoring

---

## 7. COMPLETE FILE REFERENCE

| File | Role | Consistency Usage |
|------|------|-------------------|
| `/Users/agent/strukto/mirage/mirage/types.py` | Enum definition | Defines 4 policies |
| `/Users/agent/strukto/mirage/mirage/workspace/cache/policy.py` | Core logic | `is_fresh()` enforces policies |
| `/Users/agent/strukto/mirage/mirage/workspace/workspace.py` | Read operations | Calls `is_fresh()` in `cat()` method |
| `/Users/agent/strukto/mirage/mirage/workspace/mount.py` | Mount storage | Stores policy on Mount object |
| `/Users/agent/strukto/mirage/mirage/server/service/local.py` | Service layer | Parses config, creates Workspace |
| `/Users/agent/strukto/mirage/mirage/server/fastapi/config.py` | API models | Request models with defaults |
| `/Users/agent/strukto/mirage/mirage/server/fastapi/routers/workspace_router.py` | API endpoint | Routes to service |
| `/Users/agent/strukto/mirage/mirage/cli/config.py` | CLI parsing | Parses `#consistency` suffixes |
| `/Users/agent/strukto/mirage/mirage/cli/commands/workspace.py` | CLI command | Mount command with consistency option |
| `/Users/agent/strukto/mirage/mirage/cli/commands/command.py` | Remote server | Uses CACHED by default |

---

This traces the complete data flow from API request through to actual cache validation logic. The critical decision point is in `is_fresh()` function which implements all four policies.

agentId: a17e4150a4f9d65dd (for resuming to continue this agent's work if needed)
<usage>total_tokens: 50381
tool_uses: 19
duration_ms: 55281</usage>

Prompt:
I need to understand how ConsistencyPolicy is used throughout the mirage codebase. Trace all usages comprehensively:

1. Find the enum definition in mirage/types.py
2. Find ALL files that reference ConsistencyPolicy
3. Read the key files that USE consistency (not just pass it through), especially:
   - mirage/workspace/cache/policy.py (is_fresh function)
   - mirage/workspace/workspace.py (how cat/read use it)
   - mirage/server/service/local.py (how remote server uses it)
   - mirage/server/fastapi/ routers (how API endpoints handle it)
   - mirage/runtime/ (any runtime usage)

For each file, explain HOW consistency is actually used (not just imported/passed). I want to understand the data flow from API request → workspace → cache → backend.


The user wants to understand how ConsistencyPolicy is used in the workspace and runtime, especially for remote deployments. Let me also check if there's any runtime-specific usage of consistency.


Here's how `ConsistencyPolicy` works across the system:

## How it's actually used

Consistency only matters during **reads** — specifically in `Workspace.cat()`. The flow is:

1. Check cache for the file
2. If cached, look up the mount's `ConsistencyPolicy`
3. Call `is_fresh()` in [policy.py](mirage/workspace/cache/policy.py) to decide whether to trust the cache

## The 4 policies

| Policy | `is_fresh()` behavior | Backend calls on cache hit |
|--------|----------------------|---------------------------|
| **CACHED** | Always fresh — return cached data immediately | None |
| **ALWAYS** | Never fresh — always fetch from backend | `cat()` every time |
| **TTL** | Fresh if `now - cached_at < ttl_seconds`, else check metadata | `stat()` after timeout |
| **LAZY** | Always check metadata (etag/mtime/size) against cache | `stat()` every time |

## Where it's set

- **Per-mount** on the `Mount` object (stored in `MountRegistry`)
- Default is `CACHED` at every level (workspace constructor, API, CLI)
- CLI supports URI suffixes: `s3://bucket/data#ttl:/data@read`

## Remote workspace

For a remote server deployment, the flow is:

```
HTTP POST /workspaces {consistency: "ttl", ttl_seconds: 60}
  → LocalService.create_workspace()
    → ConsistencyPolicy("ttl")
      → Workspace(consistency=TTL, ttl_seconds=60)
        → Mount.consistency = TTL for each mount
```

Then every `cat()` call checks: "was this cached less than 60 seconds ago?" If yes, return cache. If no, call `backend.stat()` to compare etag/mtime/size.

## What's NOT covered

- **Write path**: No consistency checks — writes go to cache + dirty tracker, synced later
- **Runtime**: No direct consistency usage — runtimes use the workspace API, which handles it internally
- **Other read ops** (`ls`, `stat`, `head`, `tail`, `read`, `grep`): Only `cat()` uses the cache/consistency system. All others go directly to the backend

That last point is notable — `head()`, `tail()`, `read()` (range read), `grep()`, and `ls_l()` all bypass the cache entirely and always hit the backend. Only `cat()` benefits from consistency-driven caching.

Want me to extend consistency to other read operations, or is the current `cat()`-only design intentional?


I need to understand how ConsistencyPolicy is used throughout the mirage codebase. Trace all usages comprehensively:

1. Find the enum definition in mirage/types.py
2. Find ALL files that reference ConsistencyPolicy
3. Read the key files that USE consistency (not just pass it through), especially:
   - mirage/workspace/cache/policy.py (is_fresh function)
   - mirage/workspace/workspace.py (how cat/read use it)
   - mirage/server/service/local.py (how remote server uses it)
   - mirage/server/fastapi/ routers (how API endpoints handle it)
   - mirage/runtime/ (any runtime usage)

For each file, explain HOW consistency is actually used (not just imported/passed). I want to understand the data flow from API request → workspace → cache → backend.

Prompt:
I need to understand how ConsistencyPolicy is used throughout the mirage codebase. Trace all usages comprehensively:

1. Find the enum definition in mirage/types.py
2. Find ALL files that reference ConsistencyPolicy
3. Read the key files that USE consistency (not just pass it through), especially:
   - mirage/workspace/cache/policy.py (is_fresh function)
   - mirage/workspace/workspace.py (how cat/read use it)
   - mirage/server/service/local.py (how remote server uses it)
   - mirage/server/fastapi/ routers (how API endpoints handle it)
   - mirage/runtime/ (any runtime usage)

For each file, explain HOW consistency is actually used (not just imported/passed). I want to understand the data flow from API request → workspace → cache → backend.


     1→from enum import Enum
     2→
     3→from pydantic import BaseModel, ConfigDict, Field
     4→
     5→
     6→class FileType(str, Enum):
     7→    DIRECTORY = "directory"
     8→    TEXT = "text"
     9→    BINARY = "binary"
    10→    JSON = "json"
    11→    CSV = "csv"
    12→    IMAGE_PNG = "image/png"
    13→    IMAGE_JPEG = "image/jpeg"
    14→    IMAGE_GIF = "image/gif"
    15→    ZIP = "application/zip"
    16→    GZIP = "application/gzip"
    17→    PDF = "application/pdf"
    18→
    19→
    20→class CostLevel(str, Enum):
    21→    CHEAP = "cheap"
    22→    PARTIAL = "partial"
    23→    EXPENSIVE = "expensive"
    24→    NETWORK = "network"
    25→
    26→
    27→class FileStat(BaseModel):
    28→    model_config = ConfigDict(frozen=True)
    29→
    30→    name: str
    31→    size: int | None = None
    32→    modified: str | None = None
    33→    type: FileType | None = None
    34→    extra: dict = Field(default_factory=dict)
    35→
    36→
    37→class SyncPolicy(str, Enum):
    38→    STAGED = "staged"
    39→    IMMEDIATE = "immediate"
    40→    MANUAL = "manual"
    41→
    42→
    43→class MountMode(str, Enum):
    44→    READ = "read"
    45→    WRITE = "write"
    46→    EXEC = "exec"
    47→
    48→
    49→class ConsistencyPolicy(str, Enum):
    50→    CACHED = "cached"
    51→    LAZY = "lazy"
    52→    TTL = "ttl"
    53→    ALWAYS = "always"
    54→
    55→
    56→class RuntimeType(str, Enum):
    57→    MEMORY = "memory"
    58→    LOCAL = "local"
    59→    REMOTE = "remote"
    60→
    61→
    62→class BackendType(str, Enum):
    63→    MEMORY = "memory"
    64→    LOCAL = "local"
    65→    S3 = "s3"
    66→    R2 = "r2"
    67→    HTTP = "http"
    68→    HTTPS = "https"
    69→
    70→
    71→class Inode(BaseModel):
    72→    virtual_path: str
    73→    backend_mount: str
    74→    checksum: str | None = None
    75→    size: int | None = None
    76→    remote_checksum: str | None = None
    77→    dirty: bool = False
    78→
    79→
    80→class SyncResult(BaseModel):
    81→    model_config = ConfigDict(frozen=True)
    82→
    83→    path: str
    84→    op: str
    85→    status: str
    86→    remote: str | None = None
    87→    bytes: int | None = None
    88→    duration_ms: int = 0
    89→    error: str | None = None
    90→
    91→
    92→class TransferStats(BaseModel):
    93→    bytes_read: int = 0
    94→    bytes_written: int = 0
    95→    read_ops: int = 0
    96→    write_ops: int = 0
    97→    cache_hits: int = 0
    98→    cache_misses: int = 0
    99→    total_read_ms: int = 0
   100→    total_write_ms: int = 0
   101→
   102→
   103→class CommandStats(BaseModel):
   104→    total_commands: int = 0
   105→    total_errors: int = 0
   106→    last_command: str = ""
   107→    last_timestamp: str = ""
   108→    command_counts: dict[str, int] = Field(default_factory=dict)
   109→
   110→
   111→class FilePayload(BaseModel):
   112→    checksum: str
   113→    data: bytes | None = None
   114→
   115→
   116→class RemoteCommandOptions(BaseModel):
   117→    max_file_size: str = "100MB"
   118→    cache: str = "enabled"
   119→
   120→
   121→class CommandResult(BaseModel):
   122→    model_config = ConfigDict(arbitrary_types_allowed=True)
   123→
   124→    stdout: str = ""
   125→    output_files: dict[str, bytes] = {}
   126→


Found 10 files
mirage/server/service/local.py
mirage/workspace/mount.py
mirage/workspace/workspace.py
mirage/server/fastapi/config.py
mirage/cli/commands/workspace.py
mirage/cli/config.py
mirage/cli/commands/command.py
mirage/workspace/cache/policy.py
mirage/types.py
mirage/__init__.py


     1→import time
     2→
     3→from mirage.backend.base import BaseBackend
     4→from mirage.types import ConsistencyPolicy, FileStat
     5→from mirage.workspace.cache.entry import CacheEntry
     6→
     7→
     8→def is_fresh(
     9→    entry: CacheEntry,
    10→    consistency: ConsistencyPolicy,
    11→    ttl_seconds: int,
    12→    backend: BaseBackend,
    13→    backend_path: str,
    14→) -> tuple[bool, FileStat | None]:
    15→    if consistency == ConsistencyPolicy.CACHED:
    16→        return True, None
    17→    if consistency == ConsistencyPolicy.ALWAYS:
    18→        return False, None
    19→    if consistency == ConsistencyPolicy.TTL:
    20→        if (time.monotonic() - entry.cached_at) < ttl_seconds:
    21→            return True, None
    22→    try:
    23→        remote = backend.stat(backend_path)
    24→    except FileNotFoundError:
    25→        return False, None
    26→    return metadata_matches(entry, remote), remote
    27→
    28→
    29→def metadata_matches(entry: CacheEntry, remote: FileStat) -> bool:
    30→    remote_etag = remote.extra.get("etag")
    31→    if entry.etag and remote_etag:
    32→        return entry.etag == remote_etag
    33→    if entry.modified and remote.modified:
    34→        return entry.modified == remote.modified
    35→    if entry.size is not None and remote.size is not None:
    36→        return entry.size == remote.size
    37→    return False
    38→


     1→from __future__ import annotations
     2→
     3→import time
     4→
     5→from mirage.backend.base import BaseBackend
     6→from mirage.backend.filetype import guess_type
     7→from mirage.types import (ConsistencyPolicy, FileStat, Inode, MountMode,
     8→                          SyncPolicy, SyncResult, TransferStats)
     9→from mirage.workspace.cache import LRUCache
    10→from mirage.workspace.cache.policy import is_fresh
    11→from mirage.workspace.mount import MountRegistry
    12→from mirage.workspace.sync import DirtyTracker
    13→
    14→
    15→class _PathLock:
    16→
    17→    def __init__(self, ws: Workspace, paths: list[str]) -> None:
    18→        self._ws = ws
    19→        self._paths = paths
    20→
    21→    def __enter__(self) -> _PathLock:
    22→        return self
    23→
    24→    def __exit__(self, *_: object) -> None:
    25→        self._ws.unlock_paths(self._paths)
    26→
    27→
    28→class Workspace:
    29→    """
    30→    Unified virtual filesystem over heterogeneous backends.
    31→
    32→    Mounts backends at virtual path prefixes and provides a command-level API
    33→    (cat, ls, write, cp, mv, rm, ...) with transparent caching
    34→    and write staging.
    35→
    36→    Example::
    37→
    38→        ws = Workspace(
    39→            {"/data/": S3Backend(S3Config(bucket="my-bucket")),
    40→             "/tmp/":  MemoryBackend()},
    41→            cache_limit="256MB",
    42→            sync_policy=SyncPolicy.STAGED,
    43→        )
    44→        content = ws.cat("/data/reports/q1.json")
    45→        ws.write("/tmp/summary.txt", b"hello")
    46→        ws.sync()
    47→    """
    48→
    49→    def __init__(
    50→        self,
    51→        backends: dict[str, BaseBackend | tuple],
    52→        cache_limit: str | int = "512MB",
    53→        sync_policy: SyncPolicy = SyncPolicy.STAGED,
    54→        mode: MountMode = MountMode.READ,
    55→        consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
    56→        ttl_seconds: int = 300,
    57→    ) -> None:
    58→        self._registry = MountRegistry()
    59→        self._cache = LRUCache(cache_limit)
    60→        self._tracker = DirtyTracker()
    61→        self._sync_policy = sync_policy
    62→        self._ttl_seconds = ttl_seconds
    63→        self._locked_paths: set[str] = set()
    64→        self._transfer_stats: dict[str, TransferStats] = {}
    65→        self._created_at: float = time.monotonic()
    66→
    67→        for prefix, value in backends.items():
    68→            if isinstance(value, tuple) and len(value) == 3:
    69→                backend, mount_mode, mount_consistency = value
    70→            elif isinstance(value, tuple):
    71→                backend, mount_mode = value
    72→                mount_consistency = consistency
    73→            else:
    74→                backend = value
    75→                mount_mode = mode
    76→                mount_consistency = consistency
    77→            self._registry.mount(prefix, backend, mount_mode,
    78→                                 mount_consistency)
    79→
    80→    # ── mount management ────────────────────────────────────────────────────
    81→
    82→    def mount(self,
    83→              prefix: str,
    84→              backend: BaseBackend,
    85→              mode: MountMode = MountMode.READ,
    86→              consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
    87→        self._registry.mount(prefix, backend, mode, consistency)
    88→
    89→    def backend(self, path: str) -> BaseBackend:
    90→        backend, _, _ = self._registry.resolve(path)
    91→        return backend
    92→
    93→    # ── read ops ────────────────────────────────────────────────────────────
    94→
    95→    def cat(self, path: str) -> bytes:
    96→        inode = self._tracker.get(path)
    97→        if inode and inode.dirty:
    98→            entry = self._cache.get(path)
    99→            if entry is not None:
   100→                self._record_transfer(path,
   101→                                      "read",
   102→                                      len(entry.data),
   103→                                      0,
   104→                                      cache_hit=True)
   105→                return entry.data
   106→
   107→        entry = self._cache.get(path)
   108→        remote_stat = None
   109→        if entry is not None:
   110→            consistency = self._registry.mount_for(path).consistency
   111→            backend, bp, _ = self._registry.resolve(path)
   112→            fresh, remote_stat = is_fresh(entry, consistency,
   113→                                          self._ttl_seconds, backend, bp)
   114→            if fresh:
   115→                self._record_transfer(path,
   116→                                      "read",
   117→                                      len(entry.data),
   118→                                      0,
   119→                                      cache_hit=True)
   120→                return entry.data
   121→        else:
   122→            backend, bp, _ = self._registry.resolve(path)
   123→
   124→        t0 = time.monotonic()
   125→        data = backend.cat(bp)
   126→        duration_ms = int((time.monotonic() - t0) * 1000)
   127→        self._record_transfer(path,
   128→                              "read",
   129→                              len(data),
   130→                              duration_ms,
   131→                              cache_hit=False)
   132→        if remote_stat is None:
   133→            remote_stat = backend.stat(bp)
   134→        self._cache.put(path,
   135→                        data,
   136→                        etag=remote_stat.extra.get("etag"),
   137→                        modified=remote_stat.modified,
   138→                        size=remote_stat.size)
   139→        self._tracker.on_read(path, data, self._mount_prefix(path))
   140→        return data
   141→
   142→    def ls(self, path: str) -> list[str]:
   143→        backend, bp, _ = self._registry.resolve(path)
   144→        prefix = self._mount_prefix(path).rstrip("/")
   145→        backend_ok = False
   146→        try:
   147→            backend_entries = backend.ls(bp)
   148→            backend_ok = True
   149→        except FileNotFoundError:
   150→            backend_entries = []
   151→        result: set[str] = {
   152→            prefix + "/" + e.lstrip("/")
   153→            for e in backend_entries
   154→        }
   155→        norm_path = "/" + path.strip("/")
   156→        dir_prefix = norm_path.rstrip("/") + "/"
   157→        has_staged_children = False
   158→        for inode in self._tracker.dirty():
   159→            vp = inode.virtual_path
   160→            if not vp.startswith(dir_prefix):
   161→                continue
   162→            if inode.checksum is None:
   163→                result.discard(vp)
   164→                continue
   165→            has_staged_children = True
   166→            remainder = vp[len(dir_prefix):]
   167→            first = remainder.split("/")[0]
   168→            if not first:
   169→                continue
   170→            if "/" in remainder:
   171→                result.add(dir_prefix + first + "/")
   172→            else:
   173→                result.add(dir_prefix + first)
   174→        if not backend_ok and not has_staged_children:
   175→            raise FileNotFoundError(path)
   176→        return sorted(result)
   177→
   178→    def ls_l(self, path: str) -> list[FileStat]:
   179→        backend, bp, _ = self._registry.resolve(path)
   180→        return backend.ls_l(bp)
   181→
   182→    def stat(self, path: str) -> FileStat:
   183→        inode = self._tracker.get(path)
   184→        if inode and inode.dirty and inode.checksum is not None:
   185→            entry = self._cache.get(path)
   186→            size = len(entry.data) if entry is not None else inode.size
   187→            return FileStat(
   188→                name=path.rstrip("/").rsplit("/", 1)[-1],
   189→                size=size,
   190→                type=guess_type(path),
   191→            )
   192→        backend, bp, _ = self._registry.resolve(path)
   193→        return backend.stat(bp)
   194→
   195→    def exists(self, path: str) -> bool:
   196→        try:
   197→            self.stat(path)
   198→            return True
   199→        except (FileNotFoundError, ValueError):
   200→            return False
   201→
   202→    def head(self, path: str, lines: int = 10) -> bytes:
   203→        backend, bp, _ = self._registry.resolve(path)
   204→        return backend.head(bp, lines)
   205→
   206→    def tail(self, path: str, lines: int = 10) -> bytes:
   207→        backend, bp, _ = self._registry.resolve(path)
   208→        return backend.tail(bp, lines)
   209→
   210→    def read(self, path: str, offset: int, size: int) -> bytes:
   211→        inode = self._tracker.get(path)
   212→        if inode and inode.dirty:
   213→            entry = self._cache.get(path)
   214→            if entry is not None:
   215→                return entry.data[offset:offset + size]
   216→        backend, bp, _ = self._registry.resolve(path)
   217→        return backend.read(bp, offset, size)
   218→
   219→    def grep(self, pattern: str, path: str) -> list[str]:
   220→        backend, bp, _ = self._registry.resolve(path)
   221→        return backend.grep(pattern, bp)
   222→
   223→    # ── write ops ───────────────────────────────────────────────────────────
   224→
   225→    def write(self, path: str, data: bytes) -> None:
   226→        self._assert_not_locked(path)
   227→        self._assert_writable(path)
   228→        backend, bp, _ = self._registry.resolve(path)
   229→        self._cache.put(path, data)
   230→        self._tracker.on_write(path, data, self._mount_prefix(path))
   231→        self._record_transfer(path, "write", len(data), 0)
   232→        if self._sync_policy == SyncPolicy.IMMEDIATE:
   233→            backend.put(bp, data)
   234→            self._tracker.on_read(path, data, self._mount_prefix(path))
   235→
   236→    def mkdir(self, path: str) -> None:
   237→        self._assert_writable(path)
   238→        backend, bp, _ = self._registry.resolve(path)
   239→        backend.mkdir(bp)
   240→
   241→    def rm(self, path: str) -> None:
   242→        self._assert_writable(path)
   243→        backend, bp, _ = self._registry.resolve(path)
   244→        inode = self._tracker.get(path)
   245→        staged_only = inode and inode.dirty and inode.remote_checksum is None
   246→        if not staged_only:
   247→            backend.unlink(bp)
   248→        self._cache.invalidate(path)
   249→        self._tracker.on_delete(path)
   250→
   251→    def mv(self, src: str, dst: str) -> None:
   252→        self._assert_writable(src)
   253→        sb, sbp, _ = self._registry.resolve(src)
   254→        db, dbp, _ = self._registry.resolve(dst)
   255→        src_inode = self._tracker.get(src)
   256→        staged_only = (src_inode and src_inode.dirty
   257→                       and src_inode.remote_checksum is None)
   258→        if staged_only:
   259→            entry = self._cache.get(src)
   260→            data = entry.data if entry else b""
   261→            self._cache.put(dst, data)
   262→            self._tracker.on_write(dst, data, self._mount_prefix(dst))
   263→            self._cache.invalidate(src)
   264→            self._tracker.on_delete(src)
   265→        elif sb is db:
   266→            sb.rename(sbp, dbp)
   267→            self._cache.invalidate(src)
   268→        else:
   269→            data = sb.cat(sbp)
   270→            db.put(dbp, data)
   271→            sb.unlink(sbp)
   272→            self._cache.invalidate(src)
   273→
   274→    def cp(self, src: str, dst: str) -> None:
   275→        self._assert_writable(dst)
   276→        data = self.cat(src)
   277→        db, dbp, _ = self._registry.resolve(dst)
   278→        db.put(dbp, data)
   279→
   280→    # ── sync lifecycle ──────────────────────────────────────────────────────
   281→
   282→    def sync(self, path: str | None = None) -> list[SyncResult]:
   283→        results: list[SyncResult] = []
   284→        targets = [
   285→            i for i in self._tracker.dirty()
   286→            if path is None or i.virtual_path == path
   287→        ]
   288→        for inode in targets:
   289→            backend, bp, _ = self._registry.resolve(inode.virtual_path)
   290→            entry = self._cache.get(inode.virtual_path)
   291→            data = entry.data if entry else None
   292→            result = self._tracker.on_sync(inode.virtual_path, bp, data,
   293→                                           backend)
   294→            results.append(result)
   295→        return results
   296→
   297→    def status(self) -> list[Inode]:
   298→        return self._tracker.status()
   299→
   300→    def reset(self) -> None:
   301→        self._tracker.reset()
   302→        self._cache.clear()
   303→
   304→    # ── path locking ─────────────────────────────────────────────────────────
   305→
   306→    def _lock_path(self, path: str) -> None:
   307→        self._locked_paths.add(path)
   308→
   309→    def unlock_paths(self, paths: list[str]) -> None:
   310→        for p in paths:
   311→            self._locked_paths.discard(p)
   312→
   313→    def lock_paths(self, paths: list[str]) -> _PathLock:
   314→        for p in paths:
   315→            self._lock_path(p)
   316→        return _PathLock(self, paths)
   317→
   318→    def _assert_not_locked(self, path: str) -> None:
   319→        if path in self._locked_paths:
   320→            raise PermissionError(
   321→                f"path {path!r} is locked by remote execution")
   322→
   323→    # ── helpers ─────────────────────────────────────────────────────────────
   324→
   325→    def _mount_prefix(self, path: str) -> str:
   326→        norm = "/" + path.strip("/")
   327→        for m in self._registry.mounts():
   328→            if norm == m.prefix.rstrip("/") or norm.startswith(m.prefix):
   329→                return m.prefix
   330→        return "/"
   331→
   332→    def _assert_writable(self, path: str) -> None:
   333→        _, _, mode = self._registry.resolve(path)
   334→        if mode == MountMode.READ:
   335→            raise PermissionError(f"mount at {path!r} is read-only")
   336→
   337→    def _record_transfer(self,
   338→                         path: str,
   339→                         op: str,
   340→                         nbytes: int,
   341→                         duration_ms: int,
   342→                         cache_hit: bool | None = None) -> None:
   343→        prefix = self._mount_prefix(path)
   344→        if prefix not in self._transfer_stats:
   345→            self._transfer_stats[prefix] = TransferStats()
   346→        stats = self._transfer_stats[prefix]
   347→        if op == "read":
   348→            stats.bytes_read += nbytes
   349→            stats.read_ops += 1
   350→            stats.total_read_ms += duration_ms
   351→            if cache_hit is True:
   352→                stats.cache_hits += 1
   353→            elif cache_hit is False:
   354→                stats.cache_misses += 1
   355→        elif op == "write":
   356→            stats.bytes_written += nbytes
   357→            stats.write_ops += 1
   358→            stats.total_write_ms += duration_ms
   359→


     1→from mirage.backend.base import BaseBackend
     2→from mirage.types import ConsistencyPolicy, MountMode
     3→
     4→
     5→class Mount:
     6→
     7→    def __init__(
     8→            self,
     9→            prefix: str,
    10→            backend: BaseBackend,
    11→            mode: MountMode = MountMode.READ,
    12→            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
    13→        stripped = prefix.strip("/")
    14→        self.prefix = "/" + stripped + "/" if stripped else "/"
    15→        self.backend = backend
    16→        self.mode = mode
    17→        self.consistency = consistency
    18→
    19→
    20→class MountRegistry:
    21→    """
    22→    Longest-prefix-match router.
    23→
    24→    Given a virtual path like "/s3-prod/data/file.json", resolves to the
    25→    backend mounted at "/s3-prod/" and returns the stripped path
    26→    "/data/file.json".
    27→    """
    28→
    29→    def __init__(self) -> None:
    30→        self._mounts: list[Mount] = []
    31→
    32→    def mount(
    33→            self,
    34→            prefix: str,
    35→            backend: BaseBackend,
    36→            mode: MountMode = MountMode.READ,
    37→            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
    38→        m = Mount(prefix, backend, mode, consistency)
    39→        for existing in self._mounts:
    40→            if existing.prefix == m.prefix:
    41→                raise ValueError(f"duplicate mount prefix: {m.prefix!r}")
    42→        self._mounts.append(m)
    43→        self._mounts.sort(key=lambda x: len(x.prefix), reverse=True)
    44→
    45→    def resolve(self, path: str) -> tuple[BaseBackend, str, MountMode]:
    46→        """
    47→        Returns (backend, backend_path, mode).
    48→
    49→        backend_path has the mount prefix stripped.
    50→        Raises ValueError if no mount matches.
    51→        """
    52→        norm = "/" + path.strip("/")
    53→        # ensure trailing slash when checking prefix match for directories
    54→        for m in self._mounts:
    55→            if norm == m.prefix.rstrip("/") or norm.startswith(m.prefix):
    56→                backend_path = "/" + norm[len(m.prefix):]
    57→                return m.backend, backend_path, m.mode
    58→        raise ValueError(f"no mount matches path: {path!r}")
    59→
    60→    def mount_for(self, path: str) -> Mount:
    61→        norm = "/" + path.strip("/")
    62→        for m in self._mounts:
    63→            if norm == m.prefix.rstrip("/") or norm.startswith(m.prefix):
    64→                return m
    65→        raise ValueError(f"no mount matches path: {path!r}")
    66→
    67→    def mounts(self) -> list[Mount]:
    68→        return list(self._mounts)
    69→