     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 (FileStat, Inode, MountMode, SyncPolicy, SyncResult,
     8→                          TransferStats)
     9→from mirage.workspace.cache import LRUCache
    10→from mirage.workspace.consistency import ConsistencyPolicy, 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(
    83→            self,
    84→            prefix: str,
    85→            backend: BaseBackend,
    86→            mode: MountMode = MountMode.READ,
    87→            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
    88→        self._registry.mount(prefix, backend, mode, consistency)
    89→
    90→    def backend(self, path: str) -> BaseBackend:
    91→        backend, _, _ = self._registry.resolve(path)
    92→        return backend
    93→
    94→    # ── read ops ────────────────────────────────────────────────────────────
    95→
    96→    def cat(self, path: str) -> bytes:
    97→        inode = self._tracker.get(path)
    98→        if inode and inode.dirty:
    99→            entry = self._cache.get(path)
   100→            if entry is not None:
   101→                self._record_transfer(path,
   102→                                      "read",
   103→                                      len(entry.data),
   104→                                      0,
   105→                                      cache_hit=True)
   106→                return entry.data
   107→
   108→        entry = self._cache.get(path)
   109→        remote_stat = None
   110→        if entry is not None:
   111→            consistency = self._registry.mount_for(path).consistency
   112→            backend, bp, _ = self._registry.resolve(path)
   113→            fresh, remote_stat = is_fresh(entry, consistency,
   114→                                          self._ttl_seconds, backend, bp)
   115→            if fresh:
   116→                self._record_transfer(path,
   117→                                      "read",
   118→                                      len(entry.data),
   119→                                      0,
   120→                                      cache_hit=True)
   121→                return entry.data
   122→        else:
   123→            backend, bp, _ = self._registry.resolve(path)
   124→
   125→        t0 = time.monotonic()
   126→        data = backend.cat(bp)
   127→        duration_ms = int((time.monotonic() - t0) * 1000)
   128→        self._record_transfer(path,
   129→                              "read",
   130→                              len(data),
   131→                              duration_ms,
   132→                              cache_hit=False)
   133→        if remote_stat is None:
   134→            remote_stat = backend.stat(bp)
   135→        self._cache.put(path,
   136→                        data,
   137→                        etag=remote_stat.extra.get("etag"),
   138→                        modified=remote_stat.modified,
   139→                        size=remote_stat.size)
   140→        self._tracker.on_read(path, data, self._mount_prefix(path))
   141→        return data
   142→
   143→    def ls(self, path: str) -> list[str]:
   144→        backend, bp, _ = self._registry.resolve(path)
   145→        prefix = self._mount_prefix(path).rstrip("/")
   146→        backend_ok = False
   147→        try:
   148→            backend_entries = backend.ls(bp)
   149→            backend_ok = True
   150→        except FileNotFoundError:
   151→            backend_entries = []
   152→        result: set[str] = {
   153→            prefix + "/" + e.lstrip("/")
   154→            for e in backend_entries
   155→        }
   156→        norm_path = "/" + path.strip("/")
   157→        dir_prefix = norm_path.rstrip("/") + "/"
   158→        has_staged_children = False
   159→        for inode in self._tracker.dirty():
   160→            vp = inode.virtual_path
   161→            if not vp.startswith(dir_prefix):
   162→                continue
   163→            if inode.checksum is None:
   164→                result.discard(vp)
   165→                continue
   166→            has_staged_children = True
   167→            remainder = vp[len(dir_prefix):]
   168→            first = remainder.split("/")[0]
   169→            if not first:
   170→                continue
   171→            if "/" in remainder:
   172→                result.add(dir_prefix + first + "/")
   173→            else:
   174→                result.add(dir_prefix + first)
   175→        if not backend_ok and not has_staged_children:
   176→            raise FileNotFoundError(path)
   177→        return sorted(result)
   178→
   179→    def ls_l(self, path: str) -> list[FileStat]:
   180→        backend, bp, _ = self._registry.resolve(path)
   181→        return backend.ls_l(bp)
   182→
   183→    def stat(self, path: str) -> FileStat:
   184→        inode = self._tracker.get(path)
   185→        if inode and inode.dirty and inode.checksum is not None:
   186→            entry = self._cache.get(path)
   187→            size = len(entry.data) if entry is not None else inode.size
   188→            return FileStat(
   189→                name=path.rstrip("/").rsplit("/", 1)[-1],
   190→                size=size,
   191→                type=guess_type(path),
   192→            )
   193→        backend, bp, _ = self._registry.resolve(path)
   194→        return backend.stat(bp)
   195→
   196→    def exists(self, path: str) -> bool:
   197→        try:
   198→            self.stat(path)
   199→            return True
   200→        except (FileNotFoundError, ValueError):
   201→            return False
   202→
   203→    def head(self, path: str, lines: int = 10) -> bytes:
   204→        backend, bp, _ = self._registry.resolve(path)
   205→        return backend.head(bp, lines)
   206→
   207→    def tail(self, path: str, lines: int = 10) -> bytes:
   208→        backend, bp, _ = self._registry.resolve(path)
   209→        return backend.tail(bp, lines)
   210→
   211→    def read(self, path: str, offset: int, size: int) -> bytes:
   212→        inode = self._tracker.get(path)
   213→        if inode and inode.dirty:
   214→            entry = self._cache.get(path)
   215→            if entry is not None:
   216→                return entry.data[offset:offset + size]
   217→        backend, bp, _ = self._registry.resolve(path)
   218→        return backend.read(bp, offset, size)
   219→
   220→    def grep(self, pattern: str, path: str) -> list[str]:
   221→        backend, bp, _ = self._registry.resolve(path)
   222→        return backend.grep(pattern, bp)
   223→
   224→    # ── write ops ───────────────────────────────────────────────────────────
   225→
   226→    def write(self, path: str, data: bytes) -> None:
   227→        self._assert_not_locked(path)
   228→        self._assert_writable(path)
   229→        backend, bp, _ = self._registry.resolve(path)
   230→        self._cache.put(path, data)
   231→        self._tracker.on_write(path, data, self._mount_prefix(path))
   232→        self._record_transfer(path, "write", len(data), 0)
   233→        if self._sync_policy == SyncPolicy.IMMEDIATE:
   234→            backend.put(bp, data)
   235→            self._tracker.on_read(path, data, self._mount_prefix(path))
   236→
   237→    def mkdir(self, path: str) -> None:
   238→        self._assert_writable(path)
   239→        backend, bp, _ = self._registry.resolve(path)
   240→        backend.mkdir(bp)
   241→
   242→    def rm(self, path: str) -> None:
   243→        self._assert_writable(path)
   244→        backend, bp, _ = self._registry.resolve(path)
   245→        inode = self._tracker.get(path)
   246→        staged_only = inode and inode.dirty and inode.remote_checksum is None
   247→        if not staged_only:
   248→            backend.unlink(bp)
   249→        self._cache.invalidate(path)
   250→        self._tracker.on_delete(path)
   251→
   252→    def mv(self, src: str, dst: str) -> None:
   253→        self._assert_writable(src)
   254→        sb, sbp, _ = self._registry.resolve(src)
   255→        db, dbp, _ = self._registry.resolve(dst)
   256→        src_inode = self._tracker.get(src)
   257→        staged_only = (src_inode and src_inode.dirty
   258→                       and src_inode.remote_checksum is None)
   259→        if staged_only:
   260→            entry = self._cache.get(src)
   261→            data = entry.data if entry else b""
   262→            self._cache.put(dst, data)
   263→            self._tracker.on_write(dst, data, self._mount_prefix(dst))
   264→            self._cache.invalidate(src)
   265→            self._tracker.on_delete(src)
   266→        elif sb is db:
   267→            sb.rename(sbp, dbp)
   268→            self._cache.invalidate(src)
   269→        else:
   270→            data = sb.cat(sbp)
   271→            db.put(dbp, data)
   272→            sb.unlink(sbp)
   273→            self._cache.invalidate(src)
   274→
   275→    def cp(self, src: str, dst: str) -> None:
   276→        self._assert_writable(dst)
   277→        data = self.cat(src)
   278→        db, dbp, _ = self._registry.resolve(dst)
   279→        db.put(dbp, data)
   280→
   281→    # ── sync lifecycle ──────────────────────────────────────────────────────
   282→
   283→    def sync(self, path: str | None = None) -> list[SyncResult]:
   284→        results: list[SyncResult] = []
   285→        targets = [
   286→            i for i in self._tracker.dirty()
   287→            if path is None or i.virtual_path == path
   288→        ]
   289→        for inode in targets:
   290→            backend, bp, _ = self._registry.resolve(inode.virtual_path)
   291→            entry = self._cache.get(inode.virtual_path)
   292→            data = entry.data if entry else None
   293→            result = self._tracker.on_sync(inode.virtual_path, bp, data,
   294→                                           backend)
   295→            results.append(result)
   296→        return results
   297→
   298→    def status(self) -> list[Inode]:
   299→        return self._tracker.status()
   300→
   301→    def reset(self) -> None:
   302→        self._tracker.reset()
   303→        self._cache.clear()
   304→
   305→    # ── path locking ─────────────────────────────────────────────────────────
   306→
   307→    def _lock_path(self, path: str) -> None:
   308→        self._locked_paths.add(path)
   309→
   310→    def unlock_paths(self, paths: list[str]) -> None:
   311→        for p in paths:
   312→            self._locked_paths.discard(p)
   313→
   314→    def lock_paths(self, paths: list[str]) -> _PathLock:
   315→        for p in paths:
   316→            self._lock_path(p)
   317→        return _PathLock(self, paths)
   318→
   319→    def _assert_not_locked(self, path: str) -> None:
   320→        if path in self._locked_paths:
   321→            raise PermissionError(
   322→                f"path {path!r} is locked by remote execution")
   323→
   324→    # ── helpers ─────────────────────────────────────────────────────────────
   325→
   326→    def _mount_prefix(self, path: str) -> str:
   327→        norm = "/" + path.strip("/")
   328→        for m in self._registry.mounts():
   329→            if norm == m.prefix.rstrip("/") or norm.startswith(m.prefix):
   330→                return m.prefix
   331→        return "/"
   332→
   333→    def _assert_writable(self, path: str) -> None:
   334→        _, _, mode = self._registry.resolve(path)
   335→        if mode == MountMode.READ:
   336→            raise PermissionError(f"mount at {path!r} is read-only")
   337→
   338→    def _record_transfer(self,
   339→                         path: str,
   340→                         op: str,
   341→                         nbytes: int,
   342→                         duration_ms: int,
   343→                         cache_hit: bool | None = None) -> None:
   344→        prefix = self._mount_prefix(path)
   345→        if prefix not in self._transfer_stats:
   346→            self._transfer_stats[prefix] = TransferStats()
   347→        stats = self._transfer_stats[prefix]
   348→        if op == "read":
   349→            stats.bytes_read += nbytes
   350→            stats.read_ops += 1
   351→            stats.total_read_ms += duration_ms
   352→            if cache_hit is True:
   353→                stats.cache_hits += 1
   354→            elif cache_hit is False:
   355→                stats.cache_misses += 1
   356→        elif op == "write":
   357→            stats.bytes_written += nbytes
   358→            stats.write_ops += 1
   359→            stats.total_write_ms += duration_ms
   360→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


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

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→from pydantic import BaseModel
     2→
     3→from mirage.types import MountMode, SyncPolicy
     4→from mirage.workspace.consistency import ConsistencyPolicy
     5→
     6→
     7→class MountConfig(BaseModel):
     8→    uri: str | None = None
     9→    backend: str | None = None
    10→    args: dict[str, str] | None = None
    11→    mode: MountMode | None = None
    12→    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED
    13→
    14→
    15→class CreateWorkspaceRequest(BaseModel):
    16→    mounts: dict[str, str | MountConfig]
    17→    sync_policy: SyncPolicy = SyncPolicy.STAGED
    18→    mode: MountMode = MountMode.WRITE
    19→    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED
    20→    ttl_seconds: int = 300
    21→    runtime: str | None = None
    22→    fuse_ops: dict[str, str] | None = None
    23→
    24→
    25→class RegisterFuseRequest(BaseModel):
    26→    mountpoint: str
    27→    pid: int
    28→    mount_specs: list[str] = []
    29→    runtime: str | None = None
    30→
    31→
    32→class FilePayloadRequest(BaseModel):
    33→    checksum: str
    34→    data: str | None = None
    35→
    36→
    37→class ExecRequest(BaseModel):
    38→    command: str
    39→    cwd: str | None = None
    40→    stdin: str | None = None
    41→    input_files: dict[str, FilePayloadRequest] | None = None
    42→
    43→
    44→class FuseReportRequest(BaseModel):
    45→    mountpoint: str
    46→    status: dict
    47→    transfers: dict = {}
    48→    ops: list[dict] = []
    49→
    50→
    51→class RegisterCommandRequest(BaseModel):
    52→    name: str
    53→    exec: str
    54→    description: str = ""
    55→
    56→
    57→class RegisterFuseOpRequest(BaseModel):
    58→    pattern: str
    59→    handler: str
    60→
    61→
    62→class ReadRequest(BaseModel):
    63→    path: str
    64→    offset: int
    65→    size: int
    66→
    67→
    68→class WriteRequest(BaseModel):
    69→    path: str
    70→    offset: int
    71→    data: str
    72→
    73→
    74→class PathRequest(BaseModel):
    75→    path: str
    76→
    77→
    78→class ReaddirRequest(BaseModel):
    79→    path: str
    80→    cursor: str | None = None
    81→
    82→
    83→class RenameRequest(BaseModel):
    84→    src: str
    85→    dst: str
    86→
    87→
    88→class TruncateRequest(BaseModel):
    89→    path: str
    90→    size: int
    91→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import os
     2→import signal
     3→import subprocess
     4→import sys
     5→import time
     6→import uuid
     7→from datetime import datetime, timezone
     8→
     9→from pydantic import BaseModel, ConfigDict, Field
    10→
    11→from mirage.backend.loader import load_backend_class
    12→from mirage.cli.config import parse_mount_uri, split_mount_spec
    13→from mirage.commands import COMMANDS, _resolve
    14→from mirage.commands.builtin import nav
    15→from mirage.commands.custom.registry import CommandRegistry, ExternalCommand
    16→from mirage.fuse import FUSE_OPS
    17→from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class
    18→from mirage.fuse.registry import FuseOpRegistry
    19→from mirage.observe.entries import (CommandEntry, CommandResult, DispatchInfo,
    20→                                    ParsedCommand, SessionEntry, WorkspaceInfo)
    21→from mirage.observe.session_log import get_or_create_log
    22→from mirage.runtime.base import _BLOCKED, CommandNotAllowedError
    23→from mirage.server.service.base import MirageService
    24→from mirage.server.service.cache import FileCache
    25→from mirage.types import (CommandStats, FileStat, MountMode, RuntimeType,
    26→                          SyncPolicy)
    27→from mirage.workspace import Workspace
    28→from mirage.workspace.consistency import ConsistencyPolicy
    29→
    30→
    31→class WorkspaceSession(BaseModel):
    32→    model_config = ConfigDict(arbitrary_types_allowed=True)
    33→
    34→    id: str
    35→    ws: Workspace | None
    36→    registry: CommandRegistry | None
    37→    cwd: str = "/"
    38→    fuse_mounted: bool = False
    39→    mode: MountMode = MountMode.WRITE
    40→    mountpoint: str | None = None
    41→    pid: int | None = None
    42→    mounts: list[dict] = Field(default_factory=list)
    43→    runtime_type: RuntimeType | None = None
    44→    fuse_registry: FuseOpRegistry = Field(default_factory=FuseOpRegistry)
    45→    command_stats: CommandStats = Field(default_factory=CommandStats)
    46→    created_at: float = Field(default_factory=time.monotonic)
    47→    reported_status: dict | None = None
    48→    reported_transfers: dict | None = None
    49→
    50→
    51→def _unmount_fuse(mountpoint: str, pid: int | None) -> None:
    52→    if sys.platform == "darwin":
    53→        subprocess.run(["diskutil", "unmount", "force", mountpoint],
    54→                       capture_output=True)
    55→    else:
    56→        subprocess.run(["fusermount", "-u", mountpoint], capture_output=True)
    57→    if pid:
    58→        try:
    59→            os.kill(pid, signal.SIGTERM)
    60→        except ProcessLookupError:
    61→            pass
    62→
    63→
    64→class LocalService(MirageService):
    65→
    66→    def __init__(self) -> None:
    67→        self._sessions: dict[str, WorkspaceSession] = {}
    68→        self._file_cache = FileCache()
    69→
    70→    def _get_session(self, ws_id: str) -> WorkspaceSession:
    71→        session = self._sessions.get(ws_id)
    72→        if session is None:
    73→            raise KeyError(f"workspace {ws_id!r} not found")
    74→        if session.fuse_mounted:
    75→            raise ValueError("not supported for fuse workspaces")
    76→        return session
    77→
    78→    def create_workspace(self, config: dict) -> str:
    79→        mode = config.get("mode", "write")
    80→        consistency = config.get("consistency", "cached")
    81→        default_mode = MountMode(mode)
    82→        default_consistency = ConsistencyPolicy(consistency)
    83→        mounts = config.get("mounts", {})
    84→
    85→        backends: dict = {}
    86→        for prefix, value in mounts.items():
    87→            if isinstance(value, str):
    88→                backends[prefix] = parse_mount_uri(value)
    89→            else:
    90→                backend_spec = value.get("backend")
    91→                if backend_spec:
    92→                    cls = load_backend_class(backend_spec)
    93→                    backend = cls(**(value.get("args") or {}))
    94→                else:
    95→                    backend = parse_mount_uri(value.get("uri") or "")
    96→                mount_mode = (MountMode(value["mode"])
    97→                              if value.get("mode") else default_mode)
    98→                mount_consistency = (ConsistencyPolicy(value["consistency"])
    99→                                     if value.get("consistency") else
   100→                                     default_consistency)
   101→                if (mount_mode != default_mode
   102→                        or mount_consistency != default_consistency):
   103→                    backends[prefix] = (backend, mount_mode, mount_consistency)
   104→                else:
   105→                    backends[prefix] = backend
   106→
   107→        ws = Workspace(
   108→            backends,
   109→            sync_policy=SyncPolicy(config.get("sync_policy", "staged")),
   110→            mode=default_mode,
   111→            consistency=default_consistency,
   112→            ttl_seconds=config.get("ttl_seconds", 300),
   113→        )
   114→
   115→        mounts_info: list[dict] = []
   116→        for prefix, value in mounts.items():
   117→            if isinstance(value, str):
   118→                uri = value
   119→            else:
   120→                uri = value.get("uri") or ""
   121→                if not uri and value.get("backend"):
   122→                    uri = f"{value['backend']}://"
   123→            mounts_info.append({"prefix": prefix, "uri": uri})
   124→
   125→        ws_id = str(uuid.uuid4())[:8]
   126→        registry = CommandRegistry()
   127→        fuse_ops_config = config.get("fuse_ops") or {}
   128→        fuse_registry = build_registry_from_config(fuse_ops_config)
   129→        runtime = config.get("runtime")
   130→        rt_type = RuntimeType(runtime) if runtime else None
   131→
   132→        self._sessions[ws_id] = WorkspaceSession(
   133→            id=ws_id,
   134→            ws=ws,
   135→            registry=registry,
   136→            mode=mode,
   137→            mounts=mounts_info,
   138→            runtime_type=rt_type,
   139→            fuse_registry=fuse_registry,
   140→        )
   141→
   142→        log = get_or_create_log(ws_id)
   143→        mount_info = {}
   144→        for m in ws._registry.mounts():
   145→            mount_info[m.prefix] = {
   146→                "type": type(m.backend).__name__,
   147→                "mode": m.mode.value,
   148→            }
   149→        session_entry = SessionEntry(
   150→            timestamp=datetime.now(timezone.utc).isoformat(),
   151→            workspace=WorkspaceInfo(
   152→                mounts=mount_info,
   153→                cache_limit=str(ws._cache.limit),
   154→                runtime_type=rt_type.value if rt_type else None,
   155→            ),
   156→            commands=registry.list_commands(),
   157→        )
   158→        log.write_session(session_entry)
   159→        return ws_id
   160→
   161→    def delete_workspace(self, ws_id: str) -> None:
   162→        session = self._sessions.get(ws_id)
   163→        if session is None:
   164→            raise KeyError(f"workspace {ws_id!r} not found")
   165→        if session.fuse_mounted and session.mountpoint:
   166→            _unmount_fuse(session.mountpoint, session.pid)
   167→        self._file_cache.clear(ws_id)
   168→        del self._sessions[ws_id]
   169→
   170→    def list_workspaces(self) -> list[dict]:
   171→        return [{
   172→            "id": s.id,
   173→            "fuse_mounted": s.fuse_mounted,
   174→            "mode": s.mode,
   175→            "mountpoint": s.mountpoint,
   176→            "mounts": s.mounts,
   177→            "runtime": s.runtime_type.value if s.runtime_type else None,
   178→        } for s in self._sessions.values()]
   179→
   180→    def exec(
   181→        self,
   182→        ws_id: str,
   183→        command: str,
   184→        cwd: str | None = None,
   185→        stdin: bytes | None = None,
   186→        input_files: dict[str, tuple[str, bytes | None]] | None = None,
   187→    ) -> tuple[bool, str, dict[str, bytes] | None, dict[str, str] | None]:
   188→        session = self._get_session(ws_id)
   189→        if cwd is not None:
   190→            session.cwd = cwd
   191→
   192→        cache_status: dict[str, str] = {}
   193→        if input_files:
   194→            for path, (checksum, data) in input_files.items():
   195→                hit, _ = self._file_cache.get(ws_id, path, checksum)
   196→                if hit:
   197→                    cache_status[path] = "hit"
   198→                elif data is not None:
   199→                    self._file_cache.put(ws_id, path, checksum, data)
   200→                    session.ws.write(path, data)
   201→                    cache_status[path] = "miss"
   202→                else:
   203→                    return (False, f"cache miss: {path}", None, None)
   204→
   205→        cmd_name = command.strip().split()[0] if command.strip() else ""
   206→        session.command_stats.total_commands += 1
   207→        session.command_stats.command_counts[cmd_name] = (
   208→            session.command_stats.command_counts.get(cmd_name, 0) + 1)
   209→        session.command_stats.last_command = command
   210→        session.command_stats.last_timestamp = datetime.now(
   211→            timezone.utc).isoformat()
   212→        t0 = time.monotonic()
   213→        try:
   214→            if session.runtime_type is not None and cmd_name in _BLOCKED:
   215→                raise CommandNotAllowedError(
   216→                    f"{cmd_name!r} is not allowed in this runtime")
   217→            output = session.registry.run(session.ws,
   218→                                          command,
   219→                                          cwd=session.cwd,
   220→                                          stdin=stdin)
   221→            duration_ms = int((time.monotonic() - t0) * 1000)
   222→            if command.strip().startswith("cd "):
   223→                parts = command.strip().split(None, 1)
   224→                if len(parts) > 1:
   225→                    session.cwd = nav.cd(session.ws, session.cwd,
   226→                                         _resolve(session.cwd, parts[1]))
   227→            args = command.strip().split()[1:] if command.strip() else []
   228→            source = "builtin" if cmd_name in COMMANDS else "external"
   229→            log = get_or_create_log(ws_id)
   230→            entry = CommandEntry(
   231→                timestamp=datetime.now(timezone.utc).isoformat(),
   232→                raw=command,
   233→                parsed=ParsedCommand(
   234→                    pipeline=[command],
   235→                    args={cmd_name: args},
   236→                ),
   237→                dispatch=[
   238→                    DispatchInfo(
   239→                        command=cmd_name,
   240→                        handler=f"_{cmd_name}"
   241→                        if source == "builtin" else cmd_name,
   242→                        source=source,
   243→                    )
   244→                ],
   245→                result=CommandResult(
   246→                    exit_code=0,
   247→                    stdout_bytes=len(output.encode()) if output else 0,
   248→                    duration_ms=duration_ms,
   249→                ),
   250→            )
   251→            log.write_command(entry)
   252→
   253→            return (True, output, None, cache_status or None)
   254→        except Exception as e:
   255→            duration_ms = int((time.monotonic() - t0) * 1000)
   256→            session.command_stats.total_errors += 1
   257→            log = get_or_create_log(ws_id)
   258→            entry = CommandEntry(
   259→                timestamp=datetime.now(timezone.utc).isoformat(),
   260→                raw=command,
   261→                result=CommandResult(exit_code=1,
   262→                                     stdout_bytes=0,
   263→                                     duration_ms=duration_ms),
   264→            )
   265→            log.write_command(entry)
   266→            return (False, str(e), None, None)
   267→
   268→    def list_commands(self, ws_id: str) -> list[str]:
   269→        session = self._get_session(ws_id)
   270→        return session.registry.list_commands()
   271→
   272→    def register_command(self,
   273→                         ws_id: str,
   274→                         name: str,
   275→                         exec_str: str,
   276→                         description: str = "") -> None:
   277→        session = self._get_session(ws_id)
   278→        session.registry.register(
   279→            ExternalCommand(name=name, exec=exec_str, description=description))
   280→
   281→    def sync(self, ws_id: str) -> list[dict]:
   282→        session = self._get_session(ws_id)
   283→        results = session.ws.sync()
   284→        return [{"path": r.path, "status": r.status} for r in results]
   285→
   286→    def status(self, ws_id: str) -> list[dict]:
   287→        session = self._get_session(ws_id)
   288→        inodes = session.ws.status()
   289→        return [{
   290→            "path": i.virtual_path,
   291→            "dirty": i.dirty,
   292→            "size": i.size
   293→        } for i in inodes]
   294→
   295→    # ── File I/O (backend-level) ─────────────────────────────────────────
   296→
   297→    def io_read(self, ws_id: str, path: str, offset: int, size: int) -> bytes:
   298→        session = self._get_session(ws_id)
   299→        backend, backend_path, _ = session.ws._registry.resolve(path)
   300→        return backend.read(backend_path, offset, size)
   301→
   302→    def io_write(self, ws_id: str, path: str, offset: int, data: bytes) -> int:
   303→        session = self._get_session(ws_id)
   304→        backend, backend_path, _ = session.ws._registry.resolve(path)
   305→        return backend.write(backend_path, offset, data)
   306→
   307→    def io_stat(self, ws_id: str, path: str) -> FileStat:
   308→        session = self._get_session(ws_id)
   309→        backend, backend_path, _ = session.ws._registry.resolve(path)
   310→        return backend.stat(backend_path)
   311→
   312→    def io_readdir(self,
   313→                   ws_id: str,
   314→                   path: str,
   315→                   cursor: str | None = None) -> tuple[list[str], str | None]:
   316→        session = self._get_session(ws_id)
   317→        backend, backend_path, _ = session.ws._registry.resolve(path)
   318→        return backend.readdir(backend_path, cursor)
   319→
   320→    def io_create(self, ws_id: str, path: str) -> None:
   321→        session = self._get_session(ws_id)
   322→        backend, backend_path, _ = session.ws._registry.resolve(path)
   323→        backend.create(backend_path)
   324→
   325→    def io_unlink(self, ws_id: str, path: str) -> None:
   326→        session = self._get_session(ws_id)
   327→        backend, backend_path, _ = session.ws._registry.resolve(path)
   328→        backend.unlink(backend_path)
   329→
   330→    def io_mkdir(self, ws_id: str, path: str) -> None:
   331→        session = self._get_session(ws_id)
   332→        backend, backend_path, _ = session.ws._registry.resolve(path)
   333→        backend.mkdir(backend_path)
   334→
   335→    def io_rmdir(self, ws_id: str, path: str) -> None:
   336→        session = self._get_session(ws_id)
   337→        backend, backend_path, _ = session.ws._registry.resolve(path)
   338→        backend.rmdir(backend_path)
   339→
   340→    def io_rename(self, ws_id: str, src: str, dst: str) -> None:
   341→        session = self._get_session(ws_id)
   342→        backend, src_path, _ = session.ws._registry.resolve(src)
   343→        _, dst_path, _ = session.ws._registry.resolve(dst)
   344→        backend.rename(src_path, dst_path)
   345→
   346→    def io_truncate(self, ws_id: str, path: str, size: int) -> None:
   347→        session = self._get_session(ws_id)
   348→        backend, backend_path, _ = session.ws._registry.resolve(path)
   349→        backend.truncate(backend_path, size)
   350→
   351→    def io_fuse_cat(self, ws_id: str, path: str) -> bytes:
   352→        session = self._get_session(ws_id)
   353→        st = session.ws.stat(path)
   354→        op = session.fuse_registry.resolve(path, st.size or 0, session.ws)
   355→        ctx = op.on_open(session.ws, path, st.size or 0)
   356→        return op.read(session.ws, path, 0, st.size or 0, ctx)
   357→
   358→    # ── FuseOp management ────────────────────────────────────────────────
   359→
   360→    def list_fuse_ops(self, ws_id: str) -> list[dict]:
   361→        session = self._get_session(ws_id)
   362→        return session.fuse_registry.list_ops()
   363→
   364→    def register_fuse_op(self, ws_id: str, pattern: str, handler: str) -> None:
   365→        session = self._get_session(ws_id)
   366→        op = FUSE_OPS.get(handler)
   367→        if op is None:
   368→            try:
   369→                cls = load_fuse_op_class(handler)
   370→                op = cls()
   371→            except (ValueError, Exception) as e:
   372→                raise ValueError(f"unknown handler: {handler} ({e})")
   373→        session.fuse_registry.register(pattern, op)
   374→
   375→    # ── FUSE-specific (not part of MirageService ABC) ────────────────────
   376→
   377→    def register_fuse(self,
   378→                      mountpoint: str,
   379→                      pid: int,
   380→                      mount_specs: list[str] | None = None,
   381→                      runtime: str | None = None) -> str:
   382→        fuse_mounts: list[dict] = []
   383→        for spec in (mount_specs or []):
   384→            try:
   385→                uri, prefix = split_mount_spec(spec)
   386→                fuse_mounts.append({"prefix": prefix, "uri": uri})
   387→            except ValueError:
   388→                pass
   389→        ws_id = str(uuid.uuid4())[:8]
   390→        rt_type = RuntimeType(runtime) if runtime else None
   391→        self._sessions[ws_id] = WorkspaceSession(
   392→            id=ws_id,
   393→            ws=None,
   394→            registry=None,
   395→            fuse_mounted=True,
   396→            mountpoint=mountpoint,
   397→            pid=pid,
   398→            mounts=fuse_mounts,
   399→            runtime_type=rt_type,
   400→        )
   401→        mount_info = {m["prefix"]: {"type": m["uri"]} for m in fuse_mounts}
   402→        log = get_or_create_log(ws_id)
   403→        session_entry = SessionEntry(
   404→            timestamp=datetime.now(timezone.utc).isoformat(),
   405→            workspace=WorkspaceInfo(
   406→                mounts=mount_info,
   407→                runtime_type=rt_type.value if rt_type else None,
   408→            ),
   409→        )
   410→        log.write_session(session_entry)
   411→        return ws_id
   412→
   413→    def report_fuse_stats(self,
   414→                          mountpoint: str,
   415→                          status: dict,
   416→                          transfers: dict | None = None,
   417→                          ops: list[dict] | None = None) -> bool:
   418→        for s in self._sessions.values():
   419→            if s.fuse_mounted and s.mountpoint == mountpoint:
   420→                s.reported_status = status
   421→                s.reported_transfers = transfers or {}
   422→                if ops:
   423→                    log = get_or_create_log(s.id)
   424→                    for op in ops:
   425→                        log._append(op)
   426→                return True
   427→        return False
   428→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import os
     2→import sys
     3→import threading
     4→import time
     5→from pathlib import Path
     6→from typing import TYPE_CHECKING, Annotated
     7→
     8→import typer
     9→
    10→from mirage.backend.loader import load_backend_class
    11→from mirage.cli.client import ClientError
    12→from mirage.cli.config import (load_workspace_yaml, parse_mount_uri,
    13→                               parse_mount_uri_full, split_mount_spec)
    14→from mirage.cli.helpers import _client, _ensure_server
    15→from mirage.fuse.loader import build_registry_from_config
    16→from mirage.fuse.registry import FuseOpRegistry
    17→from mirage.observe.inspector import (workspace_status_from_ws,
    18→                                      workspace_transfers_from_ws)
    19→from mirage.types import MountMode, RuntimeType
    20→from mirage.workspace import Workspace
    21→from mirage.workspace.consistency import ConsistencyPolicy
    22→
    23→if TYPE_CHECKING:
    24→    from mirage.fuse.fs import MirageFS
    25→
    26→workspace_app = typer.Typer(no_args_is_help=True)
    27→
    28→REPORT_INTERVAL = 2
    29→
    30→
    31→def _report_loop(ws: Workspace, mountpoint: str, fs: "MirageFS") -> None:
    32→    from mirage.cli.client import MirageClient
    33→    client = MirageClient()
    34→    time.sleep(2)
    35→    while True:
    36→        try:
    37→            status = workspace_status_from_ws(ws)
    38→            transfers = workspace_transfers_from_ws(ws)
    39→            ops = fs.drain_ops()
    40→            client.report_fuse_stats(mountpoint, status, transfers, ops)
    41→        except Exception:
    42→            pass
    43→        time.sleep(REPORT_INTERVAL)
    44→
    45→
    46→def _parse_mounts_for_fuse(
    47→    specs: list[str],
    48→    cfg: dict,
    49→    default_mode: MountMode,
    50→    default_consistency: ConsistencyPolicy,
    51→    ttl: int,
    52→    agent_id: str | None,
    53→) -> tuple[dict, MountMode, ConsistencyPolicy, int, str | None]:
    54→    resolved_agent_id = agent_id
    55→    mounts: dict = {}
    56→    if cfg:
    57→        if "consistency" in cfg:
    58→            default_consistency = ConsistencyPolicy(cfg["consistency"])
    59→        if "ttl" in cfg:
    60→            ttl = cfg["ttl"]
    61→        if not resolved_agent_id and "agent_id" in cfg:
    62→            resolved_agent_id = cfg["agent_id"]
    63→        for prefix, value in cfg.get("mounts", {}).items():
    64→            if isinstance(value, str):
    65→                backend_obj, m, c = parse_mount_uri_full(
    66→                    value, default_mode, default_consistency)
    67→            elif isinstance(value, dict) and "backend" in value:
    68→                cls = load_backend_class(value["backend"])
    69→                backend_obj = cls(**(value.get("args") or {}))
    70→                m = (MountMode(value["mode"])
    71→                     if "mode" in value else default_mode)
    72→                c = (ConsistencyPolicy(value["consistency"])
    73→                     if "consistency" in value else default_consistency)
    74→            elif isinstance(value, dict):
    75→                backend_obj = parse_mount_uri(value.get("uri", ""))
    76→                m = (MountMode(value["mode"])
    77→                     if "mode" in value else default_mode)
    78→                c = (ConsistencyPolicy(value["consistency"])
    79→                     if "consistency" in value else default_consistency)
    80→            else:
    81→                continue
    82→            has_override = (m != default_mode or c != default_consistency)
    83→            if has_override:
    84→                mounts[prefix] = (backend_obj, m, c)
    85→            else:
    86→                mounts[prefix] = backend_obj
    87→    for spec in specs:
    88→        uri, prefix = split_mount_spec(spec)
    89→        backend_obj, m, c = parse_mount_uri_full(uri, default_mode,
    90→                                                 default_consistency)
    91→        has_override = (m != default_mode or c != default_consistency)
    92→        if has_override:
    93→            mounts[prefix] = (backend_obj, m, c)
    94→        else:
    95→            mounts[prefix] = backend_obj
    96→    return mounts, default_mode, default_consistency, ttl, resolved_agent_id
    97→
    98→
    99→def mount(
   100→    mount_specs: Annotated[
   101→        list[str] | None,
   102→        typer.Argument(help="Mount specs: backend:/path "
   103→                       "(e.g. memory:/data/, s3://bucket:/). "
   104→                       "Append #cached|#lazy|#ttl|#always for "
   105→                       "consistency, @read|@write|@exec for mode.")] = None,
   106→    sync_policy: str = typer.Option("staged", help="staged or immediate"),
   107→    mode: MountMode = typer.Option(MountMode.WRITE, help="read or write"),
   108→    consistency: ConsistencyPolicy = typer.Option(ConsistencyPolicy.CACHED),
   109→    ttl: int = typer.Option(300, help="TTL in seconds for ttl consistency"),
   110→    config: str | None = typer.Option(None,
   111→                                      help="Path to YAML workspace config"),
   112→    runtime: RuntimeType = typer.Option(RuntimeType.MEMORY,
   113→                                        help="Runtime: memory or local"),
   114→    fuse: bool = typer.Option(False, help="Also mount as FUSE filesystem"),
   115→    mountpoint: str | None = typer.Option(
   116→        None, help="Local directory for FUSE mount (requires --fuse)"),
   117→    agent_id: str | None = typer.Option(
   118→        None, help="Agent identity exposed via /.mirage/whoami"),
   119→    backend: str | None = typer.Option(
   120→        None,
   121→        help="Backend URI mounted at /. Prefix specs override for subtree."),
   122→) -> None:
   123→    """Create a workspace and mount backends.
   124→
   125→    Each backend URI is routed by prefix. The longest matching prefix wins,
   126→    so /data/logs/ takes priority over /data/ for paths under /data/logs/.
   127→
   128→    With --fuse, also mounts the workspace as a local FUSE filesystem
   129→    (requires macFUSE / libfuse). The process blocks while FUSE is mounted.
   130→
   131→    URI formats:
   132→      memory             in-memory (no disk, no deps)
   133→      local              local filesystem at current directory
   134→      local:./path       local filesystem at ./path (converted to abs)
   135→      s3://bucket/pfx    AWS S3  (requires mirage[s3])
   136→      r2://account/bkt   Cloudflare R2  (requires mirage[s3])
   137→      https://host       HTTP read-only (requires mirage[http])
   138→    """
   139→    specs = list(mount_specs or [])
   140→    if backend:
   141→        specs.insert(0, f"{backend}:/")
   142→
   143→    if fuse:
   144→        _mount_fuse(specs, config, mode, consistency, ttl, agent_id,
   145→                    mountpoint, runtime)
   146→        return
   147→
   148→    _ensure_server()
   149→    mounts: dict[str, str | dict] = {}
   150→    if config:
   151→        cfg = load_workspace_yaml(config)
   152→        mounts.update(cfg.get("mounts", {}))
   153→        consistency_str = cfg.get("consistency")
   154→        if consistency_str:
   155→            consistency = ConsistencyPolicy(consistency_str)
   156→        ttl = cfg.get("ttl", ttl)
   157→    consistency_suffixes = ("#cached", "#lazy", "#ttl", "#always")
   158→    for spec in specs:
   159→        try:
   160→            uri, prefix = split_mount_spec(spec)
   161→        except ValueError as e:
   162→            raise typer.BadParameter(str(e))
   163→        mount_consistency = None
   164→        for suffix in consistency_suffixes:
   165→            if uri.endswith(suffix):
   166→                mount_consistency = suffix[1:]
   167→                uri = uri[:-len(suffix)]
   168→                break
   169→        mount_mode = None
   170→        for suffix in ("@read", "@write", "@exec"):
   171→            if uri.endswith(suffix):
   172→                mount_mode = suffix[1:]
   173→                uri = uri[:-len(suffix)]
   174→                break
   175→        entry: dict[str, str] = {"uri": uri}
   176→        if mount_mode:
   177→            entry["mode"] = mount_mode
   178→        if mount_consistency:
   179→            entry["consistency"] = mount_consistency
   180→        if len(entry) == 1:
   181→            mounts[prefix] = uri
   182→        else:
   183→            mounts[prefix] = entry
   184→    ws_id = _client().create_workspace(
   185→        mounts,
   186→        sync_policy=sync_policy,
   187→        mode=mode.value,
   188→        consistency=consistency.value,
   189→        ttl_seconds=ttl,
   190→        runtime=runtime.value,
   191→    )
   192→    print(ws_id)
   193→
   194→
   195→def _mount_fuse(
   196→    specs: list[str],
   197→    config: str | None,
   198→    mode: MountMode,
   199→    consistency: ConsistencyPolicy,
   200→    ttl: int,
   201→    agent_id: str | None,
   202→    mountpoint: str | None,
   203→    runtime: RuntimeType,
   204→) -> None:
   205→    from mirage.backend.network.proxy import ProxyBackend
   206→    from mirage.fuse.fs import MirageFS
   207→    from mirage.fuse.mount import mount as fuse_mount_fn
   208→    from mirage.fuse.remote.remote import RemoteFuseOp
   209→
   210→    if not mountpoint:
   211→        raise typer.BadParameter("--mountpoint is required when using --fuse")
   212→    if not specs and not config:
   213→        raise typer.BadParameter(
   214→            "No backends specified. Use --backend URI or provide mount specs.")
   215→    for spec in specs:
   216→        if ":" not in spec:
   217→            raise typer.BadParameter(
   218→                f"Invalid mount spec: {spec!r}. Use backend:/path")
   219→
   220→    Path(mountpoint).mkdir(parents=True, exist_ok=True)
   221→    _ensure_server()
   222→    cfg = load_workspace_yaml(config) if config else {}
   223→
   224→    if runtime == RuntimeType.REMOTE:
   225→        url = cfg.get("url", "")
   226→        ws_id_remote = cfg.get("ws_id", "")
   227→        proxy = ProxyBackend(url, ws_id_remote)
   228→        ws = Workspace({"/": proxy}, mode=MountMode.WRITE)
   229→        fuse_registry = FuseOpRegistry()
   230→        fuse_registry.set_default(RemoteFuseOp(proxy))
   231→        fs = MirageFS(ws, agent_id=agent_id, fuse_registry=fuse_registry)
   232→    else:
   233→        mounts, mode, consistency, ttl, agent_id = _parse_mounts_for_fuse(
   234→            specs, cfg, mode, consistency, ttl, agent_id)
   235→        ws = Workspace(mounts,
   236→                       mode=mode,
   237→                       consistency=consistency,
   238→                       ttl_seconds=ttl)
   239→        fuse_ops_config = cfg.get("fuse_ops", {}) if cfg else {}
   240→        fuse_registry = build_registry_from_config(fuse_ops_config)
   241→        fs = MirageFS(ws, agent_id=agent_id, fuse_registry=fuse_registry)
   242→
   243→    pid = os.getpid()
   244→    _client().register_fuse(mountpoint, pid, specs, runtime=runtime.value)
   245→
   246→    thread = threading.Thread(target=_report_loop,
   247→                              args=(ws, mountpoint, fs),
   248→                              daemon=True)
   249→    thread.start()
   250→
   251→    fuse_mount_fn(mountpoint=mountpoint, foreground=True, fs=fs)
   252→
   253→
   254→def exec_cmd(
   255→        ws_id: str = typer.Argument(),
   256→        command: list[str] = typer.Argument(),
   257→) -> None:
   258→    """Execute a command in a running workspace."""
   259→    cmd_str = " ".join(command)
   260→    try:
   261→        output = _client().exec(ws_id, cmd_str)
   262→        if output:
   263→            print(output)
   264→    except ClientError as e:
   265→        print(f"Error: {e}", file=sys.stderr)
   266→        raise typer.Exit(1)
   267→
   268→
   269→def kill(ws_id: str = typer.Argument()) -> None:
   270→    """Stop a workspace daemon."""
   271→    _client().delete_workspace(ws_id)
   272→    print(f"Killed workspace {ws_id}")
   273→
   274→
   275→def sync(ws_id: str = typer.Argument()) -> None:
   276→    """Flush dirty inodes to backends for a workspace."""
   277→    try:
   278→        results = _client().sync(ws_id)
   279→        if results:
   280→            for r in results:
   281→                print(f"{r['path']}: {r['status']}")
   282→        else:
   283→            print("All clean.")
   284→    except ClientError as e:
   285→        print(f"Error: {e}", file=sys.stderr)
   286→        raise typer.Exit(1)
   287→
   288→
   289→def status(ws_id: str = typer.Argument()) -> None:
   290→    """Show dirty/clean inode status for a workspace."""
   291→    try:
   292→        inodes = _client().status(ws_id)
   293→        if inodes:
   294→            for i in inodes:
   295→                marker = "D" if i["dirty"] else " "
   296→                print(f"{marker} {i['path']}")
   297→        else:
   298→            print("No tracked files.")
   299→    except ClientError as e:
   300→        print(f"Error: {e}", file=sys.stderr)
   301→        raise typer.Exit(1)
   302→
   303→
   304→@workspace_app.command(name="ls")
   305→def workspace_ls() -> None:
   306→    """List active workspaces."""
   307→    _ensure_server()
   308→    workspaces = _client().list_workspaces()
   309→    if not workspaces:
   310→        print("No active workspaces.")
   311→        return
   312→    for w in workspaces:
   313→        parts = [w["id"], w.get("type", "workspace")]
   314→        if w.get("mountpoint"):
   315→            parts.append(w["mountpoint"])
   316→        print("\t".join(parts))
   317→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import os
     2→
     3→import yaml
     4→
     5→from mirage.backend.base import BaseBackend
     6→from mirage.backend.local.local import LocalBackend
     7→from mirage.backend.local.memory import MemoryBackend
     8→from mirage.types import BackendType, MountMode
     9→from mirage.workspace.consistency import ConsistencyPolicy
    10→
    11→
    12→def _build_backend(type_name: str, connection: str) -> BaseBackend:
    13→    if type_name == BackendType.MEMORY:
    14→        return MemoryBackend()
    15→    if type_name == BackendType.LOCAL:
    16→        return LocalBackend(root=os.path.abspath(connection))
    17→    if type_name == BackendType.S3:
    18→        from mirage.backend.object_storage.s3 import S3Backend, S3Config
    19→        rest = connection[len("s3://"):]
    20→        bucket = rest.split("/", 1)[0]
    21→        return S3Backend(S3Config(bucket=bucket))
    22→    if type_name == BackendType.R2:
    23→        from mirage.backend.object_storage.r2 import R2Backend, R2Config
    24→        rest = connection[len("r2://"):]
    25→        parts = rest.split("/", 1)
    26→        return R2Backend(
    27→            R2Config(
    28→                account_id=parts[0],
    29→                bucket=parts[1] if len(parts) > 1 else "",
    30→                access_key_id=os.environ.get("R2_ACCESS_KEY_ID", ""),
    31→                secret_access_key=os.environ.get("R2_SECRET_ACCESS_KEY", ""),
    32→            ))
    33→    if type_name in (BackendType.HTTP, BackendType.HTTPS):
    34→        from mirage.backend.network.http import HTTPBackend, HTTPConfig
    35→        return HTTPBackend(HTTPConfig(base_url=connection))
    36→    raise ValueError(f"Unknown backend type: {type_name!r}. "
    37→                     "Use built-ins: memory, local, s3, r2, http")
    38→
    39→
    40→def parse_mount_uri(uri: str) -> BaseBackend:
    41→    if uri == BackendType.MEMORY:
    42→        return MemoryBackend()
    43→    if uri == BackendType.LOCAL:
    44→        return LocalBackend(root=os.path.abspath("."))
    45→
    46→    colon = uri.find(":")
    47→    if colon > 0 and "//" not in uri[:colon + 3]:
    48→        return _build_backend(uri[:colon], uri[colon + 1:])
    49→
    50→    if uri.startswith("s3://"):
    51→        return _build_backend("s3", uri)
    52→    if uri.startswith("r2://"):
    53→        return _build_backend("r2", uri)
    54→    if uri.startswith("http://") or uri.startswith("https://"):
    55→        return _build_backend("http", uri)
    56→
    57→    if "://" in uri:
    58→        scheme = uri.split("://")[0]
    59→        raise ValueError(
    60→            f"Unknown URI scheme {scheme!r}. "
    61→            "Accepted: s3://, r2://, https://. "
    62→            "For simple backends use: memory, local, local:./path")
    63→    return LocalBackend(root=os.path.abspath(uri))
    64→
    65→
    66→def split_mount_spec(spec: str) -> tuple[str, str]:
    67→    """Split 'backend:/path' into (backend, path).
    68→
    69→    Args:
    70→        spec (str): Mount spec like 'memory:/data/' or 's3://bucket:/data/'.
    71→
    72→    Returns:
    73→        tuple[str, str]: (backend, mount_path) pair.
    74→    """
    75→    i = len(spec) - 1
    76→    while i >= 0:
    77→        if spec[i] == ":" and i + 1 < len(spec) and spec[i + 1] == "/":
    78→            if i >= 2 and spec[i - 1:i + 2] == "://":
    79→                i -= 1
    80→                continue
    81→            return spec[:i], spec[i + 1:]
    82→        i -= 1
    83→    raise ValueError(
    84→        f"Invalid mount spec: {spec!r}. "
    85→        "Use format: backend:/path (e.g. memory:/data/, s3://bucket:/)")
    86→
    87→
    88→_MODE_SUFFIXES = ("@read", "@write", "@exec")
    89→
    90→
    91→def parse_mount_uri_with_mode(
    92→    uri: str,
    93→    default_mode: MountMode = MountMode.READ,
    94→) -> tuple[BaseBackend, MountMode]:
    95→    for suffix in _MODE_SUFFIXES:
    96→        if uri.endswith(suffix):
    97→            return parse_mount_uri(uri[:-len(suffix)]), MountMode(suffix[1:])
    98→    return parse_mount_uri(uri), default_mode
    99→
   100→
   101→_CONSISTENCY_SUFFIXES = ("#cached", "#lazy", "#ttl", "#always")
   102→
   103→
   104→def parse_mount_uri_full(
   105→    uri: str,
   106→    default_mode: MountMode = MountMode.READ,
   107→    default_consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
   108→) -> tuple[BaseBackend, MountMode, ConsistencyPolicy]:
   109→    consistency = default_consistency
   110→    for suffix in _CONSISTENCY_SUFFIXES:
   111→        if uri.endswith(suffix):
   112→            consistency = ConsistencyPolicy(suffix[1:])
   113→            uri = uri[:-len(suffix)]
   114→            break
   115→    backend, mode = parse_mount_uri_with_mode(uri, default_mode)
   116→    return backend, mode, consistency
   117→
   118→
   119→def parse_mounts_from_cli(
   120→    mount_specs: list[str],
   121→    default_mode: MountMode = MountMode.READ,
   122→) -> dict[str, BaseBackend | tuple[BaseBackend, MountMode]]:
   123→    mounts: dict[str, BaseBackend | tuple[BaseBackend, MountMode]] = {}
   124→    for spec in mount_specs:
   125→        uri, prefix = split_mount_spec(spec)
   126→        backend, mode = parse_mount_uri_with_mode(uri, default_mode)
   127→        if mode != default_mode:
   128→            mounts[prefix] = (backend, mode)
   129→        else:
   130→            mounts[prefix] = backend
   131→    return mounts
   132→
   133→
   134→def load_yaml_config(path: str) -> dict:
   135→    with open(path) as f:
   136→        return yaml.safe_load(f)
   137→
   138→
   139→def load_workspace_yaml(path: str) -> dict:
   140→    with open(path) as f:
   141→        data = yaml.safe_load(f)
   142→    mounts_raw = data.get("mounts", {})
   143→    mounts: dict[str, str | dict] = {}
   144→    for prefix, value in mounts_raw.items():
   145→        if isinstance(value, str):
   146→            mounts[prefix] = value
   147→        elif isinstance(value, dict):
   148→            backend_spec = value.get("backend")
   149→            if backend_spec:
   150→                entry: dict[str, str | dict] = {"backend": backend_spec}
   151→                if value.get("args"):
   152→                    entry["args"] = value["args"]
   153→                if value.get("mode"):
   154→                    entry["mode"] = value["mode"]
   155→                if value.get("consistency"):
   156→                    entry["consistency"] = value["consistency"]
   157→                mounts[prefix] = entry
   158→            else:
   159→                uri = value.get("uri", "")
   160→                mount_mode = value.get("mode")
   161→                mount_consistency = value.get("consistency")
   162→                entry_uri: dict[str, str] = {"uri": uri}
   163→                if mount_mode:
   164→                    entry_uri["mode"] = mount_mode
   165→                if mount_consistency:
   166→                    entry_uri["consistency"] = mount_consistency
   167→                if len(entry_uri) == 1:
   168→                    mounts[prefix] = uri
   169→                else:
   170→                    mounts[prefix] = entry_uri
   171→    result: dict = {"mounts": mounts, "commands": data.get("commands", {})}
   172→    if "consistency" in data:
   173→        result["consistency"] = data["consistency"]
   174→    if "ttl" in data:
   175→        result["ttl"] = data["ttl"]
   176→    if "agent_id" in data:
   177→        result["agent_id"] = data["agent_id"]
   178→    fuse_ops = data.get("fuse_ops", {})
   179→    if fuse_ops:
   180→        result["fuse_ops"] = fuse_ops
   181→    for key in ("runtime", "url", "ws_id", "command_url", "fuse_ops_url"):
   182→        if key in data:
   183→            result[key] = data[key]
   184→    return result
   185→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import typer
     2→
     3→from mirage.cli.config import split_mount_spec
     4→from mirage.cli.helpers import _client
     5→from mirage.types import MountMode, RuntimeType
     6→from mirage.workspace.consistency import ConsistencyPolicy
     7→
     8→command_app = typer.Typer(no_args_is_help=True)
     9→command_server_app = typer.Typer(no_args_is_help=True)
    10→
    11→
    12→@command_app.command(name="add")
    13→def command_add(
    14→        ws_id: str = typer.Argument(),
    15→        name: str = typer.Argument(),
    16→        exec_str: str = typer.Argument(),
    17→        description: str = typer.Option(""),
    18→) -> None:
    19→    """Register a user CLI command in a workspace."""
    20→    _client().register_command(ws_id, name, exec_str, description)
    21→    print(f"registered: {name}")
    22→
    23→
    24→@command_server_app.command(name="serve")
    25→def command_server_serve(
    26→    mount_specs: list[str] | None = typer.Argument(default=None),
    27→    host: str = typer.Option("0.0.0.0"),
    28→    port: int = typer.Option(8765),
    29→    transport: str = typer.Option("http", help="http or grpc"),
    30→    auth: str = typer.Option("none", help="none, bearer, basic, or apikey"),
    31→    token: str | None = typer.Option(None,
    32→                                     help="Bearer token (--auth bearer)"),
    33→    api_key: str | None = typer.Option(None, help="API key (--auth apikey)"),
    34→    api_key_header: str = typer.Option("X-API-Key"),
    35→    username: str | None = typer.Option(None, help="Username (--auth basic)"),
    36→    password: str | None = typer.Option(None, help="Password (--auth basic)"),
    37→    mode: MountMode = typer.Option(MountMode.READ),
    38→    config: str | None = typer.Option(None),
    39→    runtime: RuntimeType = typer.Option(RuntimeType.MEMORY,
    40→                                        help="Runtime: memory or local"),
    41→) -> None:
    42→    """Start a standalone command server backed by the given mounts."""
    43→    from mirage.backend.loader import load_backend_class
    44→    from mirage.cli.config import (load_workspace_yaml, parse_mount_uri,
    45→                                   parse_mount_uri_full)
    46→    from mirage.commands.remote.auth import (APIKeyAuth, BasicAuth,
    47→                                             BearerTokenAuth, NoAuth)
    48→    from mirage.workspace import Workspace
    49→
    50→    default_mode = mode
    51→    mounts: dict = {}
    52→    if config:
    53→        cfg = load_workspace_yaml(config)
    54→        if "mounts" in cfg:
    55→            for prefix, value in cfg["mounts"].items():
    56→                if isinstance(value, str):
    57→                    backend_obj, m, _ = parse_mount_uri_full(
    58→                        value, default_mode, ConsistencyPolicy.CACHED)
    59→                elif isinstance(value, dict) and "backend" in value:
    60→                    cls = load_backend_class(value["backend"])
    61→                    backend_obj = cls(**(value.get("args") or {}))
    62→                    m = MountMode(
    63→                        value["mode"]) if "mode" in value else default_mode
    64→                elif isinstance(value, dict):
    65→                    backend_obj = parse_mount_uri(value.get("uri", ""))
    66→                    m = MountMode(
    67→                        value["mode"]) if "mode" in value else default_mode
    68→                else:
    69→                    continue
    70→                mounts[prefix] = (backend_obj, m, ConsistencyPolicy.CACHED)
    71→
    72→    for spec in (mount_specs or []):
    73→        try:
    74→            uri, prefix = split_mount_spec(spec)
    75→        except ValueError as e:
    76→            raise typer.BadParameter(str(e))
    77→        backend_obj, m, _ = parse_mount_uri_full(uri, default_mode,
    78→                                                 ConsistencyPolicy.CACHED)
    79→        mounts[prefix] = (backend_obj, m, ConsistencyPolicy.CACHED)
    80→
    81→    if not mounts:
    82→        raise typer.BadParameter("No mounts specified.")
    83→
    84→    ws = Workspace(mounts, mode=default_mode)
    85→
    86→    if auth == "bearer":
    87→        if not token:
    88→            raise typer.BadParameter("--token required for bearer auth")
    89→        server_auth = BearerTokenAuth(token)
    90→    elif auth == "basic":
    91→        if not username or not password:
    92→            raise typer.BadParameter(
    93→                "--username and --password required for basic auth")
    94→        server_auth = BasicAuth(username, password)
    95→    elif auth == "apikey":
    96→        if not api_key:
    97→            raise typer.BadParameter("--api-key required for apikey auth")
    98→        server_auth = APIKeyAuth(api_key, api_key_header)
    99→    else:
   100→        server_auth = NoAuth()
   101→
   102→    if transport == "grpc":
   103→        from mirage.commands.remote.grpc.server import serve
   104→        serve(ws, server_auth, host=host, port=port)
   105→    else:
   106→        import uvicorn
   107→
   108→        from mirage.commands.remote.fastapi.app import \
   109→            command_server_app as cs_app
   110→        from mirage.commands.remote.fastapi.app import configure
   111→        configure(ws, server_auth)
   112→        uvicorn.run(cs_app, host=host, port=port, log_level="warning")
   113→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→from datetime import datetime, timezone
     2→
     3→import pytest
     4→
     5→from mirage.backend.local.memory import MemoryBackend
     6→from mirage.types import MountMode, SyncPolicy
     7→from mirage.workspace import Workspace
     8→from mirage.workspace.consistency import ConsistencyPolicy
     9→
    10→
    11→def _make_consistency_ws(consistency=ConsistencyPolicy.CACHED,
    12→                         ttl_seconds=300):
    13→    backend = MemoryBackend()
    14→    backend.put("/file.txt", b"original")
    15→    ws = Workspace(
    16→        {"/data/": backend},
    17→        mode=MountMode.WRITE,
    18→        consistency=consistency,
    19→        ttl_seconds=ttl_seconds,
    20→    )
    21→    return ws, backend
    22→
    23→
    24→def _make_ws(mode=MountMode.WRITE, sync_policy=SyncPolicy.STAGED):
    25→    backend = MemoryBackend()
    26→    return Workspace({"/data/": backend}, mode=mode,
    27→                     sync_policy=sync_policy), backend
    28→
    29→
    30→def test_consistency_none_caches_forever():
    31→    ws, backend = _make_consistency_ws(ConsistencyPolicy.CACHED)
    32→    assert ws.cat("/data/file.txt") == b"original"
    33→    backend._files["/file.txt"] = b"updated"
    34→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    35→    assert ws.cat("/data/file.txt") == b"original"
    36→
    37→
    38→def test_consistency_always_refetches():
    39→    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
    40→    assert ws.cat("/data/file.txt") == b"original"
    41→    backend._files["/file.txt"] = b"updated"
    42→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    43→    assert ws.cat("/data/file.txt") == b"updated"
    44→
    45→
    46→def test_consistency_ttl_fresh_within_window():
    47→    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=300)
    48→    assert ws.cat("/data/file.txt") == b"original"
    49→    backend._files["/file.txt"] = b"updated"
    50→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    51→    assert ws.cat("/data/file.txt") == b"original"
    52→
    53→
    54→def test_consistency_ttl_stale_after_expiry():
    55→    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=10)
    56→    assert ws.cat("/data/file.txt") == b"original"
    57→    backend._files["/file.txt"] = b"updated"
    58→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    59→    entry = ws._cache.get("/data/file.txt")
    60→    entry.cached_at -= 20
    61→    assert ws.cat("/data/file.txt") == b"updated"
    62→
    63→
    64→def test_consistency_lazy_unchanged():
    65→    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
    66→    assert ws.cat("/data/file.txt") == b"original"
    67→    assert ws.cat("/data/file.txt") == b"original"
    68→
    69→
    70→def test_consistency_lazy_changed():
    71→    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
    72→    assert ws.cat("/data/file.txt") == b"original"
    73→    backend._files["/file.txt"] = b"updated-content"
    74→    backend._modified["/file.txt"] = datetime.now(timezone.utc).isoformat()
    75→    assert ws.cat("/data/file.txt") == b"updated-content"
    76→
    77→
    78→def test_dirty_file_skips_revalidation():
    79→    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
    80→    ws.write("/data/file.txt", b"local-edit")
    81→    backend._files["/file.txt"] = b"remote-change"
    82→    assert ws.cat("/data/file.txt") == b"local-edit"
    83→
    84→
    85→def test_per_mount_consistency():
    86→    be_none = MemoryBackend()
    87→    be_none.put("/f.txt", b"v1")
    88→    be_always = MemoryBackend()
    89→    be_always.put("/f.txt", b"v1")
    90→    ws = Workspace(
    91→        {
    92→            "/a/": be_none,
    93→            "/b/": (be_always, MountMode.READ, ConsistencyPolicy.ALWAYS),
    94→        },
    95→        mode=MountMode.READ,
    96→        consistency=ConsistencyPolicy.CACHED,
    97→    )
    98→    assert ws.cat("/a/f.txt") == b"v1"
    99→    assert ws.cat("/b/f.txt") == b"v1"
   100→    be_none._files["/f.txt"] = b"v2"
   101→    be_none._modified["/f.txt"] = datetime.now(timezone.utc).isoformat()
   102→    be_always._files["/f.txt"] = b"v2"
   103→    be_always._modified["/f.txt"] = datetime.now(timezone.utc).isoformat()
   104→    assert ws.cat("/a/f.txt") == b"v1"
   105→    assert ws.cat("/b/f.txt") == b"v2"
   106→
   107→
   108→def test_write_and_cat():
   109→    ws, _ = _make_ws()
   110→    ws.write("/data/hello.txt", b"hello world")
   111→    assert ws.cat("/data/hello.txt") == b"hello world"
   112→
   113→
   114→def test_write_marks_dirty():
   115→    ws, _ = _make_ws()
   116→    ws.write("/data/file.txt", b"x")
   117→    assert any(i.virtual_path == "/data/file.txt" and i.dirty
   118→               for i in ws.status())
   119→
   120→
   121→def test_rm_staged_only():
   122→    ws, _ = _make_ws()
   123→    ws.write("/data/new.txt", b"staged")
   124→    ws.rm("/data/new.txt")
   125→    assert not ws.exists("/data/new.txt")
   126→
   127→
   128→def test_rm_persisted():
   129→    ws, backend = _make_ws()
   130→    backend.put("/remote.txt", b"remote")
   131→    ws.rm("/data/remote.txt")
   132→    assert not ws.exists("/data/remote.txt")
   133→
   134→
   135→def test_mv_staged_only():
   136→    ws, _ = _make_ws()
   137→    ws.write("/data/a.txt", b"content")
   138→    ws.mv("/data/a.txt", "/data/b.txt")
   139→    assert ws.cat("/data/b.txt") == b"content"
   140→    assert not ws.exists("/data/a.txt")
   141→
   142→
   143→def test_cp_creates_copy():
   144→    ws, backend = _make_ws()
   145→    backend.put("/src.txt", b"original")
   146→    ws.cp("/data/src.txt", "/data/copy.txt")
   147→    assert ws.cat("/data/copy.txt") == b"original"
   148→
   149→
   150→def test_write_read_only_raises():
   151→    ws, _ = _make_ws(mode=MountMode.READ)
   152→    with pytest.raises(PermissionError):
   153→        ws.write("/data/x.txt", b"x")
   154→
   155→
   156→def test_sync_staged_writes_to_backend():
   157→    ws, backend = _make_ws(sync_policy=SyncPolicy.STAGED)
   158→    ws.write("/data/out.txt", b"synced")
   159→    results = ws.sync()
   160→    assert any(r.path == "/data/out.txt" and r.status == "ok" for r in results)
   161→    assert backend.cat("/out.txt") == b"synced"
   162→
   163→
   164→def test_sync_immediate_writes_through():
   165→    ws, backend = _make_ws(sync_policy=SyncPolicy.IMMEDIATE)
   166→    ws.write("/data/live.txt", b"live data")
   167→    assert backend.cat("/live.txt") == b"live data"
   168→    assert not any(i.dirty for i in ws.status())
   169→
   170→
   171→def test_reset_clears_dirty():
   172→    ws, _ = _make_ws()
   173→    ws.write("/data/x.txt", b"x")
   174→    ws.reset()
   175→    assert ws.status() == []
   176→
   177→
   178→def test_stat_staged_file():
   179→    ws, _ = _make_ws()
   180→    ws.write("/data/stat.txt", b"hello")
   181→    stat = ws.stat("/data/stat.txt")
   182→    assert stat.size == 5
   183→
   184→
   185→def test_exists_staged_file():
   186→    ws, _ = _make_ws()
   187→    ws.write("/data/exists.txt", b"y")
   188→    assert ws.exists("/data/exists.txt")
   189→
   190→
   191→def test_lock_paths_blocks_write():
   192→    ws = Workspace({"/data/": MemoryBackend()},
   193→                   mode=MountMode.WRITE,
   194→                   sync_policy=SyncPolicy.IMMEDIATE)
   195→    ws.write("/data/file.txt", b"hello")
   196→    with ws.lock_paths(["/data/file.txt"]):
   197→        with pytest.raises(PermissionError, match="locked"):
   198→            ws.write("/data/file.txt", b"world")
   199→
   200→
   201→def test_lock_paths_unlocks_on_exit():
   202→    ws = Workspace({"/data/": MemoryBackend()},
   203→                   mode=MountMode.WRITE,
   204→                   sync_policy=SyncPolicy.IMMEDIATE)
   205→    ws.write("/data/file.txt", b"hello")
   206→    with ws.lock_paths(["/data/file.txt"]):
   207→        pass
   208→    ws.write("/data/file.txt", b"world")
   209→    assert ws.cat("/data/file.txt") == b"world"
   210→
   211→
   212→def test_lock_paths_unlocks_on_exception():
   213→    ws = Workspace({"/data/": MemoryBackend()},
   214→                   mode=MountMode.WRITE,
   215→                   sync_policy=SyncPolicy.IMMEDIATE)
   216→    ws.write("/data/file.txt", b"hello")
   217→    with pytest.raises(RuntimeError):
   218→        with ws.lock_paths(["/data/file.txt"]):
   219→            raise RuntimeError("boom")
   220→    ws.write("/data/file.txt", b"world")
   221→    assert ws.cat("/data/file.txt") == b"world"
   222→
   223→
   224→def test_lock_allows_read():
   225→    ws = Workspace({"/data/": MemoryBackend()},
   226→                   mode=MountMode.WRITE,
   227→                   sync_policy=SyncPolicy.IMMEDIATE)
   228→    ws.write("/data/file.txt", b"hello")
   229→    with ws.lock_paths(["/data/file.txt"]):
   230→        assert ws.cat("/data/file.txt") == b"hello"
   231→
   232→
   233→def test_lock_multiple_paths():
   234→    ws = Workspace({"/data/": MemoryBackend()},
   235→                   mode=MountMode.WRITE,
   236→                   sync_policy=SyncPolicy.IMMEDIATE)
   237→    ws.write("/data/a.txt", b"a")
   238→    ws.write("/data/b.txt", b"b")
   239→    with ws.lock_paths(["/data/a.txt", "/data/b.txt"]):
   240→        with pytest.raises(PermissionError, match="locked"):
   241→            ws.write("/data/a.txt", b"x")
   242→        with pytest.raises(PermissionError, match="locked"):
   243→            ws.write("/data/b.txt", b"x")
   244→
   245→
   246→def test_unlock_paths_force():
   247→    ws = Workspace({"/data/": MemoryBackend()},
   248→                   mode=MountMode.WRITE,
   249→                   sync_policy=SyncPolicy.IMMEDIATE)
   250→    ws.write("/data/file.txt", b"hello")
   251→    ws._lock_path("/data/file.txt")
   252→    with pytest.raises(PermissionError, match="locked"):
   253→        ws.write("/data/file.txt", b"world")
   254→    ws.unlock_paths(["/data/file.txt"])
   255→    ws.write("/data/file.txt", b"world")
   256→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→from mirage.types import FileStat
     2→from mirage.workspace.cache.entry import CacheEntry
     3→from mirage.workspace.consistency import metadata_matches
     4→
     5→
     6→def test_metadata_matches_etag_priority():
     7→    entry = CacheEntry(data=b"x",
     8→                       cached_at=0,
     9→                       etag="abc",
    10→                       modified="old",
    11→                       size=1)
    12→    remote = FileStat(name="f",
    13→                      size=999,
    14→                      modified="new",
    15→                      extra={"etag": "abc"})
    16→    assert metadata_matches(entry, remote) is True
    17→
    18→
    19→def test_metadata_matches_fallback_to_modified():
    20→    entry = CacheEntry(data=b"x", cached_at=0, modified="2024-01-01", size=1)
    21→    remote = FileStat(name="f", size=999, modified="2024-01-01")
    22→    assert metadata_matches(entry, remote) is True
    23→    remote2 = FileStat(name="f", size=999, modified="2024-02-01")
    24→    assert metadata_matches(entry, remote2) is False
    25→
    26→
    27→def test_metadata_matches_fallback_to_size():
    28→    entry = CacheEntry(data=b"x", cached_at=0, size=100)
    29→    remote = FileStat(name="f", size=100)
    30→    assert metadata_matches(entry, remote) is True
    31→    remote2 = FileStat(name="f", size=200)
    32→    assert metadata_matches(entry, remote2) is False
    33→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Error: File does not exist. Note: your current working directory is /Users/agent/strukto/mirage.


<tool_use_error>Sibling tool call errored</tool_use_error>


mirage/__init__.py:4:from mirage.workspace.consistency import ConsistencyPolicy
mirage/__init__.py:10:    "ConsistencyPolicy",
tests/workspace/test_workspace.py:8:from mirage.workspace.consistency import ConsistencyPolicy
tests/workspace/test_workspace.py:11:def _make_consistency_ws(consistency=ConsistencyPolicy.CACHED,
tests/workspace/test_workspace.py:31:    ws, backend = _make_consistency_ws(ConsistencyPolicy.CACHED)
tests/workspace/test_workspace.py:39:    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
tests/workspace/test_workspace.py:47:    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=300)
tests/workspace/test_workspace.py:55:    ws, backend = _make_consistency_ws(ConsistencyPolicy.TTL, ttl_seconds=10)
tests/workspace/test_workspace.py:65:    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
tests/workspace/test_workspace.py:71:    ws, backend = _make_consistency_ws(ConsistencyPolicy.LAZY)
tests/workspace/test_workspace.py:79:    ws, backend = _make_consistency_ws(ConsistencyPolicy.ALWAYS)
tests/workspace/test_workspace.py:93:            "/b/": (be_always, MountMode.READ, ConsistencyPolicy.ALWAYS),
tests/workspace/test_workspace.py:96:        consistency=ConsistencyPolicy.CACHED,
tests/cli/test_app.py:33:    from mirage.workspace.consistency import ConsistencyPolicy
tests/cli/test_app.py:37:        consistency=ConsistencyPolicy.CACHED,
tests/cli/test_config.py:10:from mirage.workspace.consistency import ConsistencyPolicy
tests/cli/test_config.py:127:    assert consistency == ConsistencyPolicy.LAZY
tests/cli/test_config.py:134:    assert consistency == ConsistencyPolicy.CACHED
mirage/cli/commands/workspace.py:21:from mirage.workspace.consistency import ConsistencyPolicy
mirage/cli/commands/workspace.py:50:    default_consistency: ConsistencyPolicy,
mirage/cli/commands/workspace.py:53:) -> tuple[dict, MountMode, ConsistencyPolicy, int, str | None]:
mirage/cli/commands/workspace.py:58:            default_consistency = ConsistencyPolicy(cfg["consistency"])
mirage/cli/commands/workspace.py:72:                c = (ConsistencyPolicy(value["consistency"])
mirage/cli/commands/workspace.py:78:                c = (ConsistencyPolicy(value["consistency"])
mirage/cli/commands/workspace.py:108:    consistency: ConsistencyPolicy = typer.Option(ConsistencyPolicy.CACHED),
mirage/cli/commands/workspace.py:155:            consistency = ConsistencyPolicy(consistency_str)
mirage/cli/commands/workspace.py:199:    consistency: ConsistencyPolicy,
mirage/cli/commands/command.py:6:from mirage.workspace.consistency import ConsistencyPolicy
mirage/cli/commands/command.py:58:                        value, default_mode, ConsistencyPolicy.CACHED)
mirage/cli/commands/command.py:70:                mounts[prefix] = (backend_obj, m, ConsistencyPolicy.CACHED)
mirage/cli/commands/command.py:78:                                                 ConsistencyPolicy.CACHED)
mirage/cli/commands/command.py:79:        mounts[prefix] = (backend_obj, m, ConsistencyPolicy.CACHED)
mirage/workspace/mount.py:3:from mirage.workspace.consistency import ConsistencyPolicy
mirage/workspace/mount.py:13:            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
mirage/workspace/mount.py:38:            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
mirage/cli/config.py:9:from mirage.workspace.consistency import ConsistencyPolicy
mirage/cli/config.py:107:    default_consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
mirage/cli/config.py:108:) -> tuple[BaseBackend, MountMode, ConsistencyPolicy]:
mirage/cli/config.py:112:            consistency = ConsistencyPolicy(suffix[1:])
mirage/workspace/consistency.py:9:class ConsistencyPolicy(str, Enum):
mirage/workspace/consistency.py:18:    consistency: ConsistencyPolicy,
mirage/workspace/consistency.py:23:    if consistency == ConsistencyPolicy.CACHED:
mirage/workspace/consistency.py:25:    if consistency == ConsistencyPolicy.ALWAYS:
mirage/workspace/consistency.py:27:    if consistency == ConsistencyPolicy.TTL:
mirage/workspace/workspace.py:10:from mirage.workspace.consistency import ConsistencyPolicy, is_fresh
mirage/workspace/workspace.py:55:        consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
mirage/workspace/workspace.py:87:            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
mirage/server/service/local.py:28:from mirage.workspace.consistency import ConsistencyPolicy
mirage/server/service/local.py:82:        default_consistency = ConsistencyPolicy(consistency)
mirage/server/service/local.py:98:                mount_consistency = (ConsistencyPolicy(value["consistency"])
mirage/server/fastapi/config.py:4:from mirage.workspace.consistency import ConsistencyPolicy
mirage/server/fastapi/config.py:12:    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED
mirage/server/fastapi/config.py:19:    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED


Grep

from mirage\.types import.*ConsistencyPolicy|from mirage\.workspace\.cache\.policy import


No matches found


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

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import os
     2→import subprocess
     3→import sys
     4→import tempfile
     5→import time
     6→from unittest.mock import patch
     7→
     8→import pytest
     9→
    10→
    11→def _run(args: list[str],
    12→         env: dict | None = None,
    13→         check: bool = True) -> subprocess.CompletedProcess:
    14→    e = os.environ.copy()
    15→    if env:
    16→        e.update(env)
    17→    return subprocess.run(
    18→        [sys.executable, "-m", "mirage.cli.app"] + args,
    19→        capture_output=True,
    20→        text=True,
    21→        env=e,
    22→        check=check,
    23→    )
    24→
    25→
    26→def test_help():
    27→    result = _run(["--help"], check=False)
    28→    assert result.returncode == 0
    29→
    30→
    31→def _fuse_defaults() -> dict:
    32→    from mirage.types import MountMode, RuntimeType
    33→    from mirage.workspace.consistency import ConsistencyPolicy
    34→    return dict(
    35→        sync_policy="staged",
    36→        mode=MountMode.READ,
    37→        consistency=ConsistencyPolicy.CACHED,
    38→        ttl=300,
    39→        config=None,
    40→        runtime=RuntimeType.MEMORY,
    41→        fuse=True,
    42→        agent_id=None,
    43→    )
    44→
    45→
    46→def test_mount_fuse_backend_mounts_at_root():
    47→    """--backend memory with --fuse mounts the backend at / prefix."""
    48→    from mirage.cli.commands.workspace import mount
    49→    with tempfile.TemporaryDirectory() as mp:
    50→        with patch("mirage.fuse.mount.mount") as mock_fuse, \

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


43:    from mirage.backend.loader import load_backend_class
44:    from mirage.cli.config import (load_workspace_yaml, parse_mount_uri,
46:    from mirage.commands.remote.auth import (APIKeyAuth, BasicAuth,
48:    from mirage.workspace import Workspace


24:    from mirage.fuse.fs import MirageFS
32:    from mirage.cli.client import MirageClient
205:    from mirage.backend.network.proxy import ProxyBackend
206:    from mirage.fuse.fs import MirageFS
207:    from mirage.fuse.mount import mount as fuse_mount_fn
208:    from mirage.fuse.remote.remote import RemoteFuseOp


mirage/workspace/workspace.py:10:from mirage.workspace.consistency import ConsistencyPolicy, is_fresh
mirage/workspace/workspace.py:113:            fresh, remote_stat = is_fresh(entry, consistency,
mirage/workspace/consistency.py:16:def is_fresh(
mirage/workspace/consistency.py:34:    return metadata_matches(entry, remote), remote
mirage/workspace/consistency.py:37:def metadata_matches(entry: CacheEntry, remote: FileStat) -> bool:
tests/workspace/cache/test_policy.py:3:from mirage.workspace.consistency import metadata_matches
tests/workspace/cache/test_policy.py:6:def test_metadata_matches_etag_priority():
tests/workspace/cache/test_policy.py:16:    assert metadata_matches(entry, remote) is True
tests/workspace/cache/test_policy.py:19:def test_metadata_matches_fallback_to_modified():
tests/workspace/cache/test_policy.py:22:    assert metadata_matches(entry, remote) is True
tests/workspace/cache/test_policy.py:24:    assert metadata_matches(entry, remote2) is False
tests/workspace/cache/test_policy.py:27:def test_metadata_matches_fallback_to_size():
tests/workspace/cache/test_policy.py:30:    assert metadata_matches(entry, remote) is True
tests/workspace/cache/test_policy.py:32:    assert metadata_matches(entry, remote2) is False


     1→from mirage.workspace.workspace import Workspace
     2→
     3→__all__ = ["Workspace"]
     4→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


/Users/agent/strukto/mirage/mirage/workspace/cache/store.py
/Users/agent/strukto/mirage/mirage/workspace/cache/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/cache/entry.py


     1→import pytest
     2→
     3→from mirage.backend.local.local import LocalBackend
     4→from mirage.backend.local.memory import MemoryBackend
     5→from mirage.cli.config import (load_workspace_yaml, load_yaml_config,
     6→                               parse_mount_uri, parse_mount_uri_full,
     7→                               parse_mount_uri_with_mode,
     8→                               parse_mounts_from_cli, split_mount_spec)
     9→from mirage.types import MountMode
    10→from mirage.workspace.consistency import ConsistencyPolicy
    11→
    12→
    13→def test_parse_memory_uri():
    14→    backend = parse_mount_uri("memory")
    15→    assert isinstance(backend, MemoryBackend)
    16→
    17→
    18→def test_parse_local_uri():
    19→    backend = parse_mount_uri("local:./scratch")
    20→    assert isinstance(backend, LocalBackend)

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Fix function-level imports in 3 files in `/Users/agent/strukto/mirage`. Rule: all imports must be at the top of the file, not inside functions.

## Issue 1: `tests/cli/test_app.py`

The helper `_fuse_defaults()` has imports inside it. Move them to the top of the file.

Read the file, find the `_fuse_defaults` function, hoist those imports to the top-level import section.

## Issue 2: `mirage/cli/commands/command.py`

The function `command_server_serve` has imports inside it. Read the file, find those imports, move them to the top of the file. Be careful not to break circular imports — if moving them to the top causes a circular import, flag it but try the move first.

## Issue 3: `mirage/cli/commands/workspace.py`

Functions `_report_loop` and/or `_mount_fuse` have imports inside them. Read the file, find them, move to top.

## After all fixes

Run:
```bash
cd /Users/agent/strukto/mirage && uv run pytest -q
```

All tests must pass.

Then run pre-commit:
```bash
cd /Users/agent/strukto/mirage && pre-commit run --all-files
```

Fix any issues it reports (excluding `no-commit-to-branch` which is expected to fail on main).

Then commit:
```bash
cd /Users/agent/strukto/mirage
git add tests/cli/test_app.py mirage/cli/commands/command.py mirage/cli/commands/workspace.py
git commit -m "fix: move function-level imports to module top level"
```

Prompt:
Fix function-level imports in 3 files in `/Users/agent/strukto/mirage`. Rule: all imports must be at the top of the file, not inside functions.

## Issue 1: `tests/cli/test_app.py`

The helper `_fuse_defaults()` has imports inside it. Move them to the top of the file.

Read the file, find the `_fuse_defaults` function, hoist those imports to the top-level import section.

## Issue 2: `mirage/cli/commands/command.py`

The function `command_server_serve` has imports inside it. Read the file, find those imports, move them to the top of the file. Be careful not to break circular imports — if moving them to the top causes a circular import, flag it but try the move first.

## Issue 3: `mirage/cli/commands/workspace.py`

Functions `_report_loop` and/or `_mount_fuse` have imports inside them. Read the file, find them, move to top.

## After all fixes

Run:
```bash
cd /Users/agent/strukto/mirage && uv run pytest -q
```

All tests must pass.

Then run pre-commit:
```bash
cd /Users/agent/strukto/mirage && pre-commit run --all-files
```

Fix any issues it reports (excluding `no-commit-to-branch` which is expected to fail on main).

Then commit:
```bash
cd /Users/agent/strukto/mirage
git add tests/cli/test_app.py mirage/cli/commands/command.py mirage/cli/commands/workspace.py
git commit -m "fix: move function-level imports to module top level"
```