     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 pytest
     2→
     3→from mirage.observe import session_log as observe_session_module
     4→from mirage.server.service.local import LocalService
     5→
     6→
     7→@pytest.fixture
     8→def svc():
     9→    s = LocalService()
    10→    observe_session_module._session_logs.clear()
    11→    yield s
    12→    s._sessions.clear()
    13→    observe_session_module._session_logs.clear()
    14→
    15→
    16→def test_create_and_list(svc):
    17→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    18→    assert isinstance(ws_id, str)
    19→    workspaces = svc.list_workspaces()
    20→    assert len(workspaces) == 1
    21→    assert workspaces[0]["id"] == ws_id
    22→
    23→
    24→def test_exec_touch_ls(svc):
    25→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    26→    ok, _, _, _ = svc.exec(ws_id, "touch /hello.txt")
    27→    assert ok
    28→    ok, output, _, _ = svc.exec(ws_id, "ls /")
    29→    assert ok
    30→    assert "hello.txt" in output
    31→
    32→
    33→def test_exec_blocked_with_runtime(svc):
    34→    ws_id = svc.create_workspace({
    35→        "mounts": {
    36→            "/": "memory"
    37→        },
    38→        "runtime": "memory",
    39→    })
    40→    ok, err, _, _ = svc.exec(ws_id, "python -c 'print(1)'")
    41→    assert not ok
    42→    assert "not allowed" in err
    43→
    44→
    45→def test_exec_not_blocked_without_runtime(svc):
    46→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    47→    ok, _, _, _ = svc.exec(ws_id, "ls /")
    48→    assert ok
    49→
    50→
    51→def test_delete_workspace(svc):
    52→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    53→    svc.delete_workspace(ws_id)
    54→    assert svc.list_workspaces() == []
    55→
    56→
    57→def test_delete_missing_raises(svc):
    58→    with pytest.raises(KeyError):
    59→        svc.delete_workspace("bad-id")
    60→
    61→
    62→def test_exec_missing_raises(svc):
    63→    with pytest.raises(KeyError):
    64→        svc.exec("bad-id", "ls /")
    65→
    66→
    67→def test_exec_on_fuse_raises(svc):
    68→    ws_id = svc.register_fuse(mountpoint="/tmp/ws", pid=99999)
    69→    with pytest.raises(ValueError, match="not supported"):
    70→        svc.exec(ws_id, "ls /")
    71→
    72→
    73→def test_sync(svc):
    74→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    75→    results = svc.sync(ws_id)
    76→    assert isinstance(results, list)
    77→
    78→
    79→def test_status(svc):
    80→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    81→    inodes = svc.status(ws_id)
    82→    assert isinstance(inodes, list)
    83→
    84→
    85→def test_list_commands(svc):
    86→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    87→    cmds = svc.list_commands(ws_id)
    88→    assert "ls" in cmds
    89→    assert "cat" in cmds
    90→
    91→
    92→def test_register_command(svc):
    93→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    94→    svc.register_command(ws_id, "hello", "echo hello")
    95→    cmds = svc.list_commands(ws_id)
    96→    assert "hello" in cmds
    97→
    98→
    99→def test_io_write_read(svc):
   100→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   101→    svc.io_create(ws_id, "/f.txt")
   102→    written = svc.io_write(ws_id, "/f.txt", 0, b"hello world")
   103→    assert written == 11
   104→    data = svc.io_read(ws_id, "/f.txt", 0, 11)
   105→    assert data == b"hello world"
   106→
   107→
   108→def test_io_stat(svc):
   109→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   110→    svc.io_create(ws_id, "/f.txt")
   111→    svc.io_write(ws_id, "/f.txt", 0, b"hello")
   112→    st = svc.io_stat(ws_id, "/f.txt")
   113→    assert st.size == 5
   114→
   115→
   116→def test_io_stat_missing(svc):
   117→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   118→    with pytest.raises(FileNotFoundError):
   119→        svc.io_stat(ws_id, "/missing.txt")
   120→
   121→
   122→def test_io_readdir(svc):
   123→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   124→    svc.io_create(ws_id, "/a.txt")
   125→    svc.io_create(ws_id, "/b.txt")
   126→    entries, cursor = svc.io_readdir(ws_id, "/")
   127→    names = [e.rsplit("/", 1)[-1] for e in entries]
   128→    assert "a.txt" in names
   129→    assert "b.txt" in names
   130→
   131→
   132→def test_io_mkdir_readdir(svc):
   133→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   134→    svc.io_mkdir(ws_id, "/subdir")
   135→    entries, _ = svc.io_readdir(ws_id, "/")
   136→    names = [e.rsplit("/", 1)[-1] for e in entries]
   137→    assert "subdir" in names
   138→
   139→
   140→def test_io_unlink(svc):
   141→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   142→    svc.io_create(ws_id, "/f.txt")
   143→    svc.io_unlink(ws_id, "/f.txt")
   144→    with pytest.raises(FileNotFoundError):
   145→        svc.io_stat(ws_id, "/f.txt")
   146→
   147→
   148→def test_io_rename(svc):
   149→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   150→    svc.io_create(ws_id, "/a.txt")
   151→    svc.io_write(ws_id, "/a.txt", 0, b"data")
   152→    svc.io_rename(ws_id, "/a.txt", "/b.txt")
   153→    with pytest.raises(FileNotFoundError):
   154→        svc.io_stat(ws_id, "/a.txt")
   155→    st = svc.io_stat(ws_id, "/b.txt")
   156→    assert st.size == 4
   157→
   158→
   159→def test_io_truncate(svc):
   160→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   161→    svc.io_create(ws_id, "/f.txt")
   162→    svc.io_write(ws_id, "/f.txt", 0, b"hello world")
   163→    svc.io_truncate(ws_id, "/f.txt", 5)
   164→    st = svc.io_stat(ws_id, "/f.txt")
   165→    assert st.size == 5
   166→
   167→
   168→def test_io_fuse_cat(svc):
   169→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   170→    svc.io_create(ws_id, "/f.txt")
   171→    svc.io_write(ws_id, "/f.txt", 0, b"hello")
   172→    data = svc.io_fuse_cat(ws_id, "/f.txt")
   173→    assert data == b"hello"
   174→
   175→
   176→def test_list_fuse_ops(svc):
   177→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   178→    ops = svc.list_fuse_ops(ws_id)
   179→    assert isinstance(ops, list)
   180→    assert any(o["pattern"] == "*" for o in ops)
   181→
   182→
   183→def test_register_fuse_op(svc):
   184→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   185→    svc.register_fuse_op(ws_id, "*.bin", "s3")
   186→    ops = svc.list_fuse_ops(ws_id)
   187→    assert any(o["pattern"] == "*.bin" and o["name"] == "s3" for o in ops)
   188→
   189→
   190→def test_register_fuse_op_unknown(svc):
   191→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   192→    with pytest.raises(ValueError, match="unknown handler"):
   193→        svc.register_fuse_op(ws_id, "*", "nonexistent")
   194→

<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.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>


183:    def stat(self, path: str) -> FileStat:
211:    def read(self, path: str, offset: int, size: int) -> bytes:
247:    def write(self, path: str, data: bytes) -> None:
304:    def sync(self, path: str | None = None) -> list[SyncResult]:
319:    def status(self) -> list[Inode]:


Edit

def test_register_fuse_op_unknown(svc):
    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    with pytest.raises(ValueError, match="unknown handler"):
        svc.register_fuse_op(ws_id, "*", "nonexistent")

def test_register_fuse_op_unknown(svc):
    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    with pytest.raises(ValueError, match="unknown handler"):
        svc.register_fuse_op(ws_id, "*", "nonexistent")

def test_io_read_uses_workspace_cache(svc):
    ws_id = svc.create_workspace({
        "mounts": {"/": "memory"},
        "consistency": "cached",
    })
    session = svc._sessions[ws_id]
    session.ws.write("/file.txt", b"hello world")
    session.ws.sync()
    data = svc.io_read(ws_id, "/file.txt", 0, 5)
    assert data == b"hello"

def test_io_stat_uses_workspace(svc):
    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    session = svc._sessions[ws_id]
    session.ws.write("/file.txt", b"hello")
    st = svc.io_stat(ws_id, "/file.txt")
    assert st.size == 5


The file /Users/agent/strukto/mirage/tests/server/service/test_local.py has been updated successfully.


Bash

uv run pytest tests/server/service/test_local.py::test_io_read_uses_workspace_cache tests/server/service/test_local.py::test_io_stat_uses_workspace --no-cov -v 2>&1

Run new tests to verify they fail (TDD red phase)


Error: Exit code 1
============================= test session starts ==============================
platform darwin -- Python 3.12.10, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/agent/strukto/mirage
configfile: pyproject.toml
plugins: anyio-4.12.1, httpx-0.36.0, cov-7.0.0
collected 2 items

tests/server/service/test_local.py .F                                    [100%]

=================================== FAILURES ===================================
_________________________ test_io_stat_uses_workspace __________________________

svc = <mirage.server.service.local.LocalService object at 0x10989bda0>

    def test_io_stat_uses_workspace(svc):
        ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
        session = svc._sessions[ws_id]
        session.ws.write("/file.txt", b"hello")
>       st = svc.io_stat(ws_id, "/file.txt")
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/server/service/test_local.py:212:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
mirage/server/service/local.py:310: in io_stat
    return backend.stat(backend_path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <mirage.backend.local.memory.MemoryBackend object at 0x10989b620>
path = '/file.txt'

    def stat(self, path: str) -> FileStat:
        p = self._norm(path)
        if p in self._dirs:
            return FileStat(name=p.rsplit("/", 1)[-1] or "/",
                            size=None,
                            modified=self._modified.get(p),
                            type=FileType.DIRECTORY)
        if p in self._files:
            data = self._files[p]
            return FileStat(
                name=p.rsplit("/", 1)[-1],
                size=len(data),
                modified=self._modified.get(p),
                type=guess_type(p),
            )
>       raise FileNotFoundError(p)
E       FileNotFoundError: /file.txt

mirage/backend/local/memory.py:84: FileNotFoundError
=========================== short test summary info ============================
FAILED tests/server/service/test_local.py::test_io_stat_uses_workspace - File...
========================= 1 failed, 1 passed in 0.06s ==========================

============================= test session starts ==============================
platform darwin -- Python 3.12.10, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/agent/strukto/mirage
configfile: pyproject.toml
plugins: anyio-4.12.1, httpx-0.36.0, cov-7.0.0
collected 2 items

tests/server/service/test_local.py .F                                    [100%]

=================================== FAILURES ===================================
_________________________ test_io_stat_uses_workspace __________________________

svc = <mirage.server.service.local.LocalService object at 0x10989bda0>

    def test_io_stat_uses_workspace(svc):
        ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
        session = svc._sessions[ws_id]
        session.ws.write("/file.txt", b"hello")
>       st = svc.io_stat(ws_id, "/file.txt")
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/server/service/test_local.py:212:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
mirage/server/service/local.py:310: in io_stat
    return backend.stat(backend_path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <mirage.backend.local.memory.MemoryBackend object at 0x10989b620>
path = '/file.txt'

    def stat(self, path: str) -> FileStat:
        p = self._norm(path)
        if p in self._dirs:
            return FileStat(name=p.rsplit("/", 1)[-1] or "/",
                            size=None,
                            modified=self._modified.get(p),
                            type=FileType.DIRECTORY)
        if p in self._files:
            data = self._files[p]
            return FileStat(
                name=p.rsplit("/", 1)[-1],
                size=len(data),
                modified=self._modified.get(p),
                type=guess_type(p),
            )
>       raise FileNotFoundError(p)
E       FileNotFoundError: /file.txt

mirage/backend/local/memory.py:84: FileNotFoundError
=========================== short test summary info ============================
FAILED tests/server/service/test_local.py::test_io_stat_uses_workspace - File...
========================= 1 failed, 1 passed in 0.06s ==========================


Edit

    def io_read(self, ws_id: str, path: str, offset: int, size: int) -> bytes:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        return backend.read(backend_path, offset, size)

    def io_read(self, ws_id: str, path: str, offset: int, size: int) -> bytes:
        session = self._get_session(ws_id)
        return session.ws.read(path, offset, size)


The file /Users/agent/strukto/mirage/mirage/server/service/local.py has been updated successfully.


    def io_stat(self, ws_id: str, path: str) -> FileStat:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        return backend.stat(backend_path)

    def io_stat(self, ws_id: str, path: str) -> FileStat:
        session = self._get_session(ws_id)
        return session.ws.stat(path)


Bash

uv run pytest tests/server/service/test_local.py --no-cov -v 2>&1

Run full test_local.py suite to verify all tests pass


============================= test session starts ==============================
platform darwin -- Python 3.12.10, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/agent/strukto/mirage
configfile: pyproject.toml
plugins: anyio-4.12.1, httpx-0.36.0, cov-7.0.0
collected 26 items

tests/server/service/test_local.py ..........................            [100%]

============================== 26 passed in 0.03s ==============================


Run full test suite and show last 3 lines


........................................................................ [ 98%]
.........                                                                [100%]
656 passed, 1 skipped in 2.52s


git add mirage/server/service/local.py tests/server/service/test_local.py && git commit -m "feat: route io_read and io_stat through Workspace for consistent caching"

Commit the changes


[main 4d6fb18] feat: route io_read and io_stat through Workspace for consistent caching
 2 files changed, 22 insertions(+), 4 deletions(-)


Ok let's analyze the inter dependency and let's see which module can be refactored out?


The user wants to analyze the inter-module dependencies in the `mirage` codebase and identify which modules can be refactored out (extracted, simplified, or reorganized). Let me use the Explore agent to analyze the dependency graph.


Agent

Analyze mirage module dependency graph

Prompt:
Analyze the inter-module dependencies in `/Users/agent/strukto/mirage/mirage/`. Build a complete dependency graph showing what imports what.

For each top-level module/subpackage, list:
1. What it imports FROM (its dependencies)
2. What imports IT (its dependents)

Focus on these modules:
- mirage/types.py
- mirage/backend/ (base, filetype, loader, local/, network/, object_storage/)
- mirage/workspace/ (workspace, mount, consistency, sync, cache/)
- mirage/fuse/ (fs, mount, registry, loader, base, backend/, filetype/, remote/)
- mirage/server/ (fastapi/, service/)
- mirage/cli/ (config, app, commands/)
- mirage/commands/ (builtin/, custom/, remote/)
- mirage/runtime/
- mirage/observe/
- mirage/integration/ (if exists)

For each module, just show:
- Direct imports of other mirage.* modules (not stdlib/third-party)

Then identify:
1. Leaf modules (no mirage deps, only stdlib/third-party)
2. Core modules (many things depend on them)
3. Any circular dependencies
4. Modules that seem like they could be extracted or simplified

Be thorough — read the actual import statements from each file.


Perfect! Now I have all the information I need. Let me compile a comprehensive dependency graph report:

## Complete Inter-Module Dependency Analysis for Mirage

Based on thorough analysis of all import statements in the codebase, here's the complete dependency graph:

---

## DEPENDENCY MATRIX BY MODULE

### **mirage/types.py** (LEAF - No mirage deps)
- **Imports FROM:** None (stdlib: Enum, Pydantic)
- **Imported BY:**
  - mirage/backend/base.py
  - mirage/backend/filetype.py
  - mirage/backend/local/local.py
  - mirage/backend/local/memory.py
  - mirage/backend/network/http.py
  - mirage/backend/network/proxy.py
  - mirage/backend/object_storage/s3.py
  - mirage/__init__.py
  - mirage/cli/config.py
  - mirage/cli/commands/command.py
  - mirage/cli/commands/workspace.py
  - mirage/commands/builtin/handlers.py
  - mirage/commands/builtin/nav.py
  - mirage/commands/builtin/read.py
  - mirage/commands/builtin/search.py
  - mirage/commands/builtin/write.py
  - mirage/commands/custom/arg_resolver.py
  - mirage/commands/custom/registry.py
  - mirage/commands/remote/client.py
  - mirage/observe/entries.py
  - mirage/runtime/base.py
  - mirage/runtime/local.py
  - mirage/runtime/remote.py
  - mirage/server/fastapi/config.py
  - mirage/server/service/local.py
  - mirage/server/service/base.py
  - mirage/workspace/consistency.py
  - mirage/workspace/mount.py
  - mirage/workspace/sync.py
  - mirage/workspace/workspace.py

**Status:** CORE TYPE DEFINITIONS - Highly central (imported by ~30 modules)

---

### **mirage/backend/base.py** (LEAF - No mirage deps except types)
- **Imports FROM:** mirage/types.py
- **Imported BY:**
  - mirage/backend/__init__.py
  - mirage/backend/filetype.py
  - mirage/backend/local/local.py
  - mirage/backend/local/memory.py
  - mirage/backend/network/http.py
  - mirage/backend/network/proxy.py
  - mirage/backend/object_storage/s3.py
  - mirage/workspace/mount.py
  - mirage/workspace/sync.py
  - mirage/workspace/workspace.py
  - mirage/workspace/consistency.py

**Status:** BASE ABSTRACTION - Core interface for all backends

---

### **mirage/backend/filetype.py** (LEAF)
- **Imports FROM:** mirage/types.py
- **Imported BY:**
  - mirage/backend/local/local.py
  - mirage/backend/local/memory.py
  - mirage/backend/network/http.py
  - mirage/backend/object_storage/s3.py
  - mirage/workspace/workspace.py

**Status:** Utility/Helper for file type detection

---

### **mirage/backend/local/local.py**
- **Imports FROM:** mirage/backend/base.py, mirage/backend/filetype.py, mirage/types.py
- **Imported BY:** mirage/backend/local/__init__.py, mirage/__init__.py, mirage/cli/config.py, mirage/runtime/local.py

**Status:** Implementation module

---

### **mirage/backend/local/memory.py**
- **Imports FROM:** mirage/backend/base.py, mirage/backend/filetype.py, mirage/types.py
- **Imported BY:** mirage/backend/local/__init__.py, mirage/__init__.py, mirage/cli/config.py, mirage/observe/inspector.py

**Status:** Implementation module

---

### **mirage/backend/network/http.py**
- **Imports FROM:** mirage/backend/base.py, mirage/backend/filetype.py, mirage/types.py
- **Imported BY:** mirage/cli/config.py

**Status:** Optional implementation (requires httpx)

---

### **mirage/backend/network/proxy.py**
- **Imports FROM:** mirage/backend/base.py, mirage/types.py
- **Imported BY:**
  - mirage/backend/network/__init__.py
  - mirage/backend/__init__.py
  - mirage/cli/commands/workspace.py
  - mirage/runtime/remote.py

**Status:** Network-based backend proxy

---

### **mirage/backend/object_storage/s3.py**
- **Imports FROM:** mirage/backend/base.py, mirage/backend/filetype.py, mirage/types.py
- **Imported BY:** mirage/cli/config.py

**Status:** Optional implementation (requires boto3)

---

### **mirage/backend/object_storage/r2.py**
- **Imports FROM:** mirage/backend/object_storage/s3.py (inherits from S3)
- **Imported BY:** mirage/cli/config.py

**Status:** Cloudflare R2 backend (extends S3)

---

### **mirage/backend/loader.py** (LEAF)
- **Imports FROM:** None (stdlib only)
- **Imported BY:**
  - mirage/cli/commands/command.py
  - mirage/cli/commands/workspace.py
  - mirage/server/service/local.py

**Status:** Dynamic backend loading utility

---

### **mirage/workspace/cache/entry.py** (LEAF)
- **Imports FROM:** None (Pydantic only)
- **Imported BY:** mirage/workspace/cache/__init__.py, mirage/workspace/cache/store.py, mirage/workspace/consistency.py

**Status:** Data model for cache entries

---

### **mirage/workspace/cache/store.py** (LEAF)
- **Imports FROM:** mirage/workspace/cache/entry.py
- **Imported BY:** mirage/workspace/cache/__init__.py, mirage/workspace/workspace.py

**Status:** LRU cache implementation

---

### **mirage/workspace/consistency.py**
- **Imports FROM:**
  - mirage/backend/base.py
  - mirage/types.py
  - mirage/workspace/cache/entry.py
- **Imported BY:**
  - mirage/workspace/mount.py
  - mirage/workspace/workspace.py
  - mirage/cli/config.py
  - mirage/cli/commands/command.py
  - mirage/cli/commands/workspace.py
  - mirage/server/fastapi/config.py
  - mirage/server/service/local.py

**Status:** Cache consistency policies

---

### **mirage/workspace/mount.py**
- **Imports FROM:**
  - mirage/backend/base.py
  - mirage/types.py
  - mirage/workspace/consistency.py
- **Imported BY:** mirage/workspace/workspace.py

**Status:** Mount registry for backends

---

### **mirage/workspace/sync.py**
- **Imports FROM:**
  - mirage/backend/base.py
  - mirage/types.py
- **Imported BY:** mirage/workspace/workspace.py

**Status:** Dirty tracking for write operations

---

### **mirage/workspace/workspace.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/backend/base.py
  - mirage/backend/filetype.py
  - mirage/types.py
  - mirage/workspace/cache/
  - mirage/workspace/consistency.py
  - mirage/workspace/mount.py
  - mirage/workspace/sync.py
- **Imported BY:** (30+ modules - see list below)

**Status:** CENTRAL - Unified filesystem abstraction

---

### **mirage/workspace/__init__.py**
- **Imports FROM:** mirage/workspace/workspace.py
- **Imported BY:** mirage/__init__.py (re-export)

---

### **mirage/fuse/base.py** (LEAF)
- **Imports FROM:** None (TYPE_CHECKING: mirage/workspace)
- **Imported BY:**
  - mirage/fuse/__init__.py
  - mirage/fuse/backend/default.py
  - mirage/fuse/backend/local/local.py
  - mirage/fuse/backend/local/memory.py
  - mirage/fuse/backend/object_storage/s3.py
  - mirage/fuse/filetype/csv.py
  - mirage/fuse/filetype/parquet.py
  - mirage/fuse/remote/remote.py
  - mirage/fuse/registry.py

**Status:** Base class for FUSE operations

---

### **mirage/fuse/fs.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/fuse/base.py
  - mirage/fuse/ops_log.py
  - mirage/fuse/registry.py
  - mirage/workspace
- **Imported BY:**
  - mirage/fuse/mount.py
  - mirage/cli/commands/workspace.py

**Status:** FUSE filesystem implementation

---

### **mirage/fuse/ops_log.py** (LEAF)
- **Imports FROM:** None
- **Imported BY:** mirage/fuse/fs.py

**Status:** Operations logging for FUSE

---

### **mirage/fuse/registry.py**
- **Imports FROM:**
  - mirage/fuse/__init__.py (FUSE_OPS, FuseOp)
- **Imported BY:**
  - mirage/fuse/fs.py
  - mirage/fuse/loader.py
  - mirage/cli/commands/workspace.py
  - mirage/runtime/base.py
  - mirage/runtime/accessors/local.py
  - mirage/server/service/local.py

**Status:** Registry for FUSE operations

---

### **mirage/fuse/loader.py**
- **Imports FROM:**
  - mirage/fuse/__init__.py
  - mirage/fuse/registry.py
- **Imported BY:**
  - mirage/cli/commands/workspace.py
  - mirage/server/service/local.py

**Status:** Dynamic FUSE operation loading

---

### **mirage/fuse/mount.py**
- **Imports FROM:**
  - mirage/fuse/fs.py
  - mirage/workspace
- **Imported BY:** mirage/cli/commands/workspace.py

**Status:** Mount FUSE filesystem

---

### **mirage/fuse/backend/default.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/backend/local/local.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/backend/local/memory.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/backend/object_storage/s3.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/filetype/csv.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py (TYPE_CHECKING: mirage/workspace)
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/filetype/parquet.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py (TYPE_CHECKING: mirage/workspace)
- **Imported BY:** mirage/fuse/__init__.py

---

### **mirage/fuse/remote/remote.py** (LEAF)
- **Imports FROM:** mirage/fuse/base.py (TYPE_CHECKING: mirage/workspace)
- **Imported BY:** mirage/cli/commands/workspace.py

---

### **mirage/fuse/__init__.py**
- **Imports FROM:** All mirage/fuse/backend/* and mirage/fuse/filetype/* modules
- **Imported BY:**
  - mirage/fuse/loader.py
  - mirage/fuse/registry.py
  - mirage/runtime/accessors/local.py
  - mirage/server/service/local.py

**Status:** Aggregates all FUSE operations

---

### **mirage/commands/builtin/handlers.py** (LEAF)
- **Imports FROM:**
  - mirage/types.py
  - mirage/workspace
  - Imports local submodules: nav, net, read, search, write
- **Imported BY:**
  - mirage/commands/__init__.py
  - mirage/commands/custom/registry.py
  - mirage/observe/inspector.py
  - mirage/server/service/local.py

**Status:** Built-in command implementations

---

### **mirage/commands/builtin/nav.py** (LEAF)
- **Imports FROM:** mirage/types.py, mirage/workspace
- **Imported BY:**
  - mirage/commands/builtin/handlers.py
  - mirage/runtime/base.py
  - mirage/server/service/local.py

---

### **mirage/commands/builtin/read.py** (LEAF)
- **Imports FROM:** mirage/types.py, mirage/workspace
- **Imported BY:** mirage/commands/builtin/handlers.py

---

### **mirage/commands/builtin/search.py** (LEAF)
- **Imports FROM:** mirage/types.py, mirage/workspace
- **Imported BY:** mirage/commands/builtin/handlers.py

---

### **mirage/commands/builtin/write.py** (LEAF)
- **Imports FROM:** mirage/types.py, mirage/workspace
- **Imported BY:** mirage/commands/builtin/handlers.py

---

### **mirage/commands/builtin/net.py**
- **Status:** Not analyzed in detail, referenced in handlers

---

### **mirage/commands/custom/arg_resolver.py** (LEAF)
- **Imports FROM:** mirage/types.py, mirage/workspace
- **Imported BY:**
  - mirage/commands/custom/registry.py
  - mirage/commands/custom/executor.py

---

### **mirage/commands/custom/executor.py** (LEAF)
- **Imports FROM:**
  - mirage/commands/custom/arg_resolver.py
  - mirage/workspace
- **Imported BY:** mirage/commands/custom/registry.py

---

### **mirage/commands/custom/registry.py**
- **Imports FROM:**
  - mirage/commands (COMMANDS from handlers)
  - mirage/commands/custom/arg_resolver.py
  - mirage/commands/custom/executor.py
  - mirage/server/constants.py
  - mirage/types.py
  - mirage/workspace
- **Imported BY:**
  - mirage/cli/commands/command.py
  - mirage/commands/remote/fastapi/app.py
  - mirage/commands/remote/grpc/server.py
  - mirage/runtime/base.py
  - mirage/runtime/local.py
  - mirage/runtime/memory.py
  - mirage/server/service/local.py

**Status:** Extensible command registry

---

### **mirage/commands/remote/auth.py** (LEAF)
- **Imports FROM:** None analyzed
- **Imported BY:**
  - mirage/commands/remote/client.py
  - mirage/commands/remote/fastapi/app.py
  - mirage/commands/remote/grpc/server.py
  - mirage/cli/commands/command.py

---

### **mirage/commands/remote/client.py**
- **Imports FROM:**
  - mirage/commands/remote/auth.py
  - mirage/commands/remote/grpc/proto/
  - mirage/types.py
- **Imported BY:** (appears to be library code for clients)

---

### **mirage/commands/remote/fastapi/app.py**
- **Imports FROM:**
  - mirage/commands/custom/registry.py
  - mirage/commands/remote/auth.py
  - mirage/workspace
- **Imported BY:** mirage/cli/commands/command.py

---

### **mirage/commands/remote/grpc/server.py**
- **Imports FROM:**
  - mirage/commands/custom/registry.py
  - mirage/commands/remote/auth.py
  - mirage/commands/remote/grpc/proto/
  - mirage/workspace
- **Imported BY:** mirage/cli/commands/command.py

---

### **mirage/commands/__init__.py**
- **Imports FROM:** mirage/commands/builtin/handlers.py
- **Imported BY:**
  - mirage/commands/custom/registry.py
  - mirage/observe/inspector.py
  - mirage/runtime/base.py
  - mirage/server/service/local.py

**Status:** Exposes built-in commands

---

### **mirage/runtime/base.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/commands (COMMANDS, _resolve)
  - mirage/commands/builtin/nav.py
  - mirage/commands/custom/registry.py
  - mirage/fuse/registry.py
  - mirage/runtime/accessors/base.py
  - mirage/runtime/accessors/local.py
  - mirage/workspace
- **Imported BY:**
  - mirage/runtime/__init__.py
  - mirage/runtime/local.py
  - mirage/runtime/memory.py
  - mirage/runtime/remote.py
  - mirage/server/service/local.py

**Status:** Runtime environment for command execution

---

### **mirage/runtime/local.py**
- **Imports FROM:**
  - mirage/backend/local/local.py
  - mirage/commands/custom/registry.py
  - mirage/runtime/base.py
  - mirage/types.py
  - mirage/workspace
- **Imported BY:** mirage/runtime/__init__.py

---

### **mirage/runtime/memory.py**
- **Imports FROM:**
  - mirage/commands/custom/registry.py
  - mirage/runtime/base.py
  - mirage/workspace
- **Imported BY:** mirage/runtime/__init__.py

---

### **mirage/runtime/remote.py**
- **Imports FROM:**
  - mirage/backend/network/proxy.py
  - mirage/runtime/accessors/remote.py
  - mirage/runtime/base.py
  - mirage/server/routes.py
  - mirage/types.py
  - mirage/workspace
- **Imported BY:** mirage/runtime/__init__.py

---

### **mirage/runtime/accessors/base.py** (LEAF)
- **Imports FROM:** None analyzed
- **Imported BY:**
  - mirage/runtime/base.py
  - mirage/runtime/accessors/local.py
  - mirage/runtime/accessors/remote.py

---

### **mirage/runtime/accessors/local.py**
- **Imports FROM:**
  - mirage/commands/custom/registry.py
  - mirage/fuse (FUSE_OPS, FuseOp)
  - mirage/fuse/loader.py
  - mirage/fuse/registry.py
  - mirage/runtime/accessors/base.py
  - mirage/workspace
- **Imported BY:** mirage/runtime/base.py

---

### **mirage/runtime/accessors/remote.py**
- **Imports FROM:**
  - mirage/runtime/accessors/base.py
  - mirage/server/routes.py
- **Imported BY:** mirage/runtime/remote.py

---

### **mirage/runtime/__init__.py**
- **Imports FROM:**
  - mirage/runtime/base.py
  - mirage/runtime/local.py
  - mirage/runtime/memory.py
  - mirage/runtime/remote.py
- **Imported BY:** (exported for public API)

---

### **mirage/server/constants.py** (LEAF)
- **Imports FROM:** None analyzed
- **Imported BY:**
  - mirage/commands/custom/registry.py
  - mirage/cli/helpers.py
  - mirage/server/fastapi/daemon.py
  - mirage/cli/client.py

---

### **mirage/server/routes.py** (LEAF)
- **Imports FROM:** None analyzed
- **Imported BY:**
  - mirage/observe/fastapi/router.py
  - mirage/server/fastapi/routers/command_router.py
  - mirage/server/fastapi/routers/io_router.py
  - mirage/server/fastapi/routers/workspace_router.py
  - mirage/runtime/accessors/remote.py
  - mirage/runtime/remote.py
  - mirage/cli/client.py

---

### **mirage/server/fastapi/config.py**
- **Imports FROM:**
  - mirage/types.py
  - mirage/workspace/consistency.py
- **Imported BY:**
  - mirage/server/fastapi/routers/command_router.py
  - mirage/server/fastapi/routers/io_router.py
  - mirage/server/fastapi/routers/workspace_router.py

---

### **mirage/server/fastapi/app.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/observe/fastapi/router.py
  - mirage/server/fastapi/routers/command_router.py
  - mirage/server/fastapi/routers/io_router.py
  - mirage/server/fastapi/routers/workspace_router.py
  - mirage/server/service/local.py
- **Imported BY:** mirage/server/fastapi/daemon.py

**Status:** FastAPI application assembly

---

### **mirage/server/fastapi/daemon.py**
- **Imports FROM:**
  - mirage/observe/__init__.py
  - mirage/server/constants.py
  - mirage/server/fastapi/app.py
- **Imported BY:** (CLI entry point)

---

### **mirage/server/fastapi/routers/command_router.py**
- **Imports FROM:**
  - mirage/server/routes.py
  - mirage/server/fastapi/config.py
- **Imported BY:** mirage/server/fastapi/app.py

---

### **mirage/server/fastapi/routers/io_router.py**
- **Imports FROM:**
  - mirage/server/routes.py
  - mirage/server/fastapi/config.py
- **Imported BY:** mirage/server/fastapi/app.py

---

### **mirage/server/fastapi/routers/workspace_router.py**
- **Imports FROM:**
  - mirage/server/routes.py
  - mirage/server/fastapi/config.py
- **Imported BY:** mirage/server/fastapi/app.py

---

### **mirage/server/service/base.py** (LEAF)
- **Imports FROM:** mirage/types.py
- **Imported BY:** mirage/server/service/local.py

---

### **mirage/server/service/cache.py** (LEAF)
- **Imports FROM:** None analyzed
- **Imported BY:** mirage/server/service/local.py

---

### **mirage/server/service/local.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/backend/loader.py
  - mirage/cli/config.py
  - mirage/commands (COMMANDS, _resolve)
  - mirage/commands/builtin/nav.py
  - mirage/commands/custom/registry.py
  - mirage/fuse (FUSE_OPS)
  - mirage/fuse/loader.py
  - mirage/fuse/registry.py
  - mirage/observe/entries.py
  - mirage/observe/session_log.py
  - mirage/runtime/base.py
  - mirage/server/service/base.py
  - mirage/server/service/cache.py
  - mirage/types.py
  - mirage/workspace
  - mirage/workspace/consistency.py
- **Imported BY:** mirage/server/fastapi/app.py

**Status:** Implements workspace service

---

### **mirage/server/__init__.py**
- **Imports FROM:** None analyzed

---

### **mirage/observe/entries.py** (LEAF)
- **Imports FROM:** mirage/types.py
- **Imported BY:**
  - mirage/observe/__init__.py
  - mirage/observe/session_log.py
  - mirage/observe/fastapi/router.py
  - mirage/server/service/local.py

---

### **mirage/observe/session_log.py** (LEAF)
- **Imports FROM:** mirage/observe/entries.py
- **Imported BY:**
  - mirage/observe/__init__.py
  - mirage/observe/fastapi/router.py
  - mirage/server/service/local.py

---

### **mirage/observe/inspector.py** (LEAF)
- **Imports FROM:**
  - mirage/backend/local/memory.py
  - mirage/commands (COMMANDS, COMMAND_COST)
  - mirage/types.py
- **Imported BY:**
  - mirage/cli/commands/workspace.py

---

### **mirage/observe/fastapi/router.py**
- **Imports FROM:**
  - mirage/observe/inspector.py
  - mirage/observe/session_log.py
  - mirage/server/routes.py
- **Imported BY:** mirage/server/fastapi/app.py

---

### **mirage/observe/__init__.py**
- **Imports FROM:**
  - mirage/observe/inspector.py
  - mirage/observe/session_log.py
- **Imported BY:**
  - mirage/server/fastapi/daemon.py

---

### **mirage/cli/config.py**
- **Imports FROM:**
  - mirage/backend/base.py
  - mirage/backend/local/local.py
  - mirage/backend/local/memory.py
  - mirage/types.py
  - mirage/workspace/consistency.py
- **Imported BY:**
  - mirage/cli/commands/command.py
  - mirage/cli/commands/workspace.py
  - mirage/server/service/local.py

---

### **mirage/cli/client.py** (LEAF)
- **Imports FROM:**
  - mirage/server/routes.py
  - mirage/server/constants.py
- **Imported BY:**
  - mirage/cli/commands/command.py
  - mirage/cli/commands/observe.py
  - mirage/cli/commands/workspace.py
  - mirage/cli/helpers.py

---

### **mirage/cli/helpers.py** (LEAF)
- **Imports FROM:**
  - mirage/cli/client.py
  - mirage/server/constants.py
- **Imported BY:**
  - mirage/cli/commands/command.py
  - mirage/cli/commands/observe.py
  - mirage/cli/commands/workspace.py

---

### **mirage/cli/commands/server.py** (LEAF)
- **Imports FROM:** mirage/server/constants.py
- **Imported BY:** mirage/cli/app.py

---

### **mirage/cli/commands/command.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/backend/loader.py
  - mirage/cli/config.py
  - mirage/cli/helpers.py
  - mirage/commands/remote/auth.py
  - mirage/commands/remote/fastapi/app.py
  - mirage/commands/remote/grpc/server.py
  - mirage/types.py
  - mirage/workspace
  - mirage/workspace/consistency.py
- **Imported BY:** mirage/cli/app.py

---

### **mirage/cli/commands/workspace.py** (CORE INTEGRATION)
- **Imports FROM:**
  - mirage/backend/loader.py
  - mirage/backend/network/proxy.py
  - mirage/cli/client.py
  - mirage/cli/config.py
  - mirage/cli/helpers.py
  - mirage/fuse/fs.py
  - mirage/fuse/loader.py
  - mirage/fuse/mount.py
  - mirage/fuse/registry.py
  - mirage/fuse/remote/remote.py
  - mirage/observe/inspector.py
  - mirage/types.py
  - mirage/workspace
  - mirage/workspace/consistency.py
- **Imported BY:** mirage/cli/app.py

---

### **mirage/cli/commands/observe.py** (LEAF)
- **Imports FROM:**
  - mirage/cli/helpers.py
- **Imported BY:** mirage/cli/app.py

---

### **mirage/cli/app.py** (ENTRY POINT)
- **Imports FROM:**
  - mirage/cli/commands/server.py
  - mirage/cli/commands/workspace.py
  - mirage/cli/commands/command.py
  - mirage/cli/commands/observe.py
- **Imported BY:** CLI entry point

---

### **mirage/__init__.py** (PUBLIC API)
- **Imports FROM:**
  - mirage/backend/local/__init__.py
  - mirage/types.py
  - mirage/workspace/__init__.py
  - mirage/workspace/consistency.py
- **Imported BY:** Package public API

---

## DEPENDENCY ANALYSIS SUMMARY

### **LEAF MODULES** (No mirage dependencies, safe for extraction/reuse):
1. mirage/types.py
2. mirage/backend/base.py
3. mirage/backend/filetype.py
4. mirage/backend/loader.py
5. mirage/backend/network/http.py
6. mirage/backend/object_storage/s3.py
7. mirage/backend/object_storage/r2.py
8. mirage/backend/local/local.py
9. mirage/backend/local/memory.py
10. mirage/fuse/base.py
11. mirage/fuse/ops_log.py
12. mirage/fuse/backend/default.py
13. mirage/fuse/backend/local/* (local.py, memory.py)
14. mirage/fuse/backend/object_storage/s3.py
15. mirage/fuse/filetype/csv.py
16. mirage/fuse/filetype/parquet.py
17. mirage/fuse/remote/remote.py
18. mirage/commands/builtin/{read,write,search,nav}.py
19. mirage/commands/custom/{arg_resolver,executor}.py
20. mirage/commands/remote/{auth,client}.py
21. mirage/server/constants.py
22. mirage/server/routes.py
23. mirage/server/service/base.py
24. mirage/server/service/cache.py
25. mirage/observe/entries.py
26. mirage/observe/session_log.py
27. mirage/observe/inspector.py
28. mirage/cli/client.py
29. mirage/cli/helpers.py
30. mirage/cli/commands/server.py
31. mirage/cli/commands/observe.py
32. mirage/workspace/cache/entry.py

### **CORE MODULES** (Many dependents, fundamental to architecture):
1. **mirage/types.py** - Imported by ~30 modules (types/enums/schemas)
2. **mirage/backend/base.py** - Imported by ~15 modules (backend abstraction)
3. **mirage/workspace/workspace.py** - Imported by ~20+ modules (central filesystem)
4. **mirage/commands/builtin/handlers.py** - Command implementations
5. **mirage/commands/custom/registry.py** - Extensible command interface
6. **mirage/runtime/base.py** - Runtime environment
7. **mirage/server/service/local.py** - Server integration
8. **mirage/cli/commands/workspace.py** - CLI mount/workspace management
9. **mirage/cli/commands/command.py** - Remote command registration
10. **mirage/server/fastapi/app.py** - FastAPI application

### **CIRCULAR DEPENDENCIES:**
**NONE DETECTED** - Clean dependency hierarchy

### **DEPENDENCY DEPTH:**
- **Level 0 (Leaf):** types, base classes, loaders, constants
- **Level 1:** Implementations (backends, filetype, cache entries)
- **Level 2:** Aggregators (mount, sync, workspace cache)
- **Level 3:** Core abstractions (workspace, consistency, base runtime)
- **Level 4:** Integrations (workspace service, runtime, commands)
- **Level 5:** CLI/Server (app, routers, daemons)

### **POTENTIAL IMPROVEMENTS:**

1. **mirage/workspace/workspace.py** is highly complex with many responsibilities:
   - Path locking
   - Cache management
   - Consistency checking
   - Dirty tracking
   - Write staging
   - **Could extract:** Lock manager, cache coordinator as separate modules

2. **mirage/server/service/local.py** has high fan-in (imports ~15 modules):
   - Could split into: workspace_service.py, command_service.py, io_service.py

3. **mirage/cli/commands/workspace.py** is large (~250+ lines):
   - Could extract mount configuration parsing to separate module
   - Could extract report loop as separate module

4. **mirage/fuse/__init__.py** aggregates many FUSE ops:
   - Clear separation by backend type (good!)
   - Could benefit from lazy loading if modules grow

5. **mirage/commands/builtin/handlers.py** acts as router:
   - Well-organized with separate modules for read/write/search
   - Consider adding plugin interface for extensibility

6. **mirage/runtime/** hierarchy is clean:
   - Good separation: base, local, memory, remote
   - Accessor pattern is well-designed

7. **No circular dependencies** - Architecture is sound

### **STRONGEST DEPENDENCIES (most relied upon):**
- mirage/types.py (foundation)
- mirage/workspace/workspace.py (integration)
- mirage/backend/base.py (abstraction)
- mirage/commands/builtin/handlers.py (built-ins)

This is a well-structured codebase with clear separation of concerns and no circular dependencies.

agentId: a21c42fd07bfb4b41 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 73601
tool_uses: 43
duration_ms: 73860</usage>

Prompt:
Analyze the inter-module dependencies in `/Users/agent/strukto/mirage/mirage/`. Build a complete dependency graph showing what imports what.

For each top-level module/subpackage, list:
1. What it imports FROM (its dependencies)
2. What imports IT (its dependents)

Focus on these modules:
- mirage/types.py
- mirage/backend/ (base, filetype, loader, local/, network/, object_storage/)
- mirage/workspace/ (workspace, mount, consistency, sync, cache/)
- mirage/fuse/ (fs, mount, registry, loader, base, backend/, filetype/, remote/)
- mirage/server/ (fastapi/, service/)
- mirage/cli/ (config, app, commands/)
- mirage/commands/ (builtin/, custom/, remote/)
- mirage/runtime/
- mirage/observe/
- mirage/integration/ (if exists)

For each module, just show:
- Direct imports of other mirage.* modules (not stdlib/third-party)

Then identify:
1. Leaf modules (no mirage deps, only stdlib/third-party)
2. Core modules (many things depend on them)
3. Any circular dependencies
4. Modules that seem like they could be extracted or simplified

Be thorough — read the actual import statements from each file.


Here's the dependency picture and what can realistically be refactored out:

## Layer diagram

```
types.py          backend/base.py         server/constants.py
    │                    │                 server/routes.py
    ▼                    ▼
backend/*           workspace/cache/
    │               workspace/consistency.py
    └──────────────► workspace/workspace.py
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
          fuse/      commands/   runtime/
              │          │          │
              └──────────┼──────────┘
                         ▼
                  server/service/local.py
                         │
                    server/fastapi/
                         │
                      cli/app.py
```

No circular deps. Clean layering.

## Refactor candidates

**1. `server/service/local.py` — split into 3** (imports 15+ modules, does everything)
- `workspace_service.py` — create/delete/list workspaces
- `io_service.py` — io_read/io_stat/io_write/io_readdir/…
- `command_service.py` — exec/register_command/sync/status + fuse management

**2. `workspace/workspace.py` — already large, extract `_PathLock` into `lock.py`**
Currently `_PathLock` + path locking logic lives inline. Minor.

**3. `cli/commands/workspace.py` — extract FUSE config parsing**
`_parse_mounts_for_fuse` is ~50 lines of parsing logic that could live in `cli/config.py` with the other parsers.

**4. `fuse/__init__.py` — lazy loading**
Currently eagerly imports all backends. Fine now, but could be lazy if optional deps (boto3, pandas) cause slow startup.

---

The biggest bang for effort is **splitting `local.py`** — it's the most tangled file (orchestrates workspace, commands, fuse, runtime, observe all in one place). Want me to do that?


Analyze the inter-module dependencies in `/Users/agent/strukto/mirage/mirage/`. Build a complete dependency graph showing what imports what.

For each top-level module/subpackage, list:
1. What it imports FROM (its dependencies)
2. What imports IT (its dependents)

Focus on these modules:
- mirage/types.py
- mirage/backend/ (base, filetype, loader, local/, network/, object_storage/)
- mirage/workspace/ (workspace, mount, consistency, sync, cache/)
- mirage/fuse/ (fs, mount, registry, loader, base, backend/, filetype/, remote/)
- mirage/server/ (fastapi/, service/)
- mirage/cli/ (config, app, commands/)
- mirage/commands/ (builtin/, custom/, remote/)
- mirage/runtime/
- mirage/observe/
- mirage/integration/ (if exists)

For each module, just show:
- Direct imports of other mirage.* modules (not stdlib/third-party)

Then identify:
1. Leaf modules (no mirage deps, only stdlib/third-party)
2. Core modules (many things depend on them)
3. Any circular dependencies
4. Modules that seem like they could be extracted or simplified

Be thorough — read the actual import statements from each file.

Prompt:
Analyze the inter-module dependencies in `/Users/agent/strukto/mirage/mirage/`. Build a complete dependency graph showing what imports what.

For each top-level module/subpackage, list:
1. What it imports FROM (its dependencies)
2. What imports IT (its dependents)

Focus on these modules:
- mirage/types.py
- mirage/backend/ (base, filetype, loader, local/, network/, object_storage/)
- mirage/workspace/ (workspace, mount, consistency, sync, cache/)
- mirage/fuse/ (fs, mount, registry, loader, base, backend/, filetype/, remote/)
- mirage/server/ (fastapi/, service/)
- mirage/cli/ (config, app, commands/)
- mirage/commands/ (builtin/, custom/, remote/)
- mirage/runtime/
- mirage/observe/
- mirage/integration/ (if exists)

For each module, just show:
- Direct imports of other mirage.* modules (not stdlib/third-party)

Then identify:
1. Leaf modules (no mirage deps, only stdlib/third-party)
2. Core modules (many things depend on them)
3. Any circular dependencies
4. Modules that seem like they could be extracted or simplified

Be thorough — read the actual import statements from each file.


Bash

find /Users/agent/strukto/mirage/mirage -type f -name "*.py" | head -30


/Users/agent/strukto/mirage/mirage/workspace/sync.py
/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
/Users/agent/strukto/mirage/mirage/workspace/consistency.py
/Users/agent/strukto/mirage/mirage/workspace/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/mount.py
/Users/agent/strukto/mirage/mirage/workspace/workspace.py
/Users/agent/strukto/mirage/mirage/fuse/ops_log.py
/Users/agent/strukto/mirage/mirage/fuse/registry.py
/Users/agent/strukto/mirage/mirage/fuse/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/memory.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/local.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/default.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/s3.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/parquet.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/csv.py
/Users/agent/strukto/mirage/mirage/fuse/loader.py
/Users/agent/strukto/mirage/mirage/fuse/fs.py
/Users/agent/strukto/mirage/mirage/fuse/mount.py
/Users/agent/strukto/mirage/mirage/fuse/base.py
/Users/agent/strukto/mirage/mirage/fuse/remote/remote.py
/Users/agent/strukto/mirage/mirage/fuse/remote/__init__.py
/Users/agent/strukto/mirage/mirage/runtime/memory.py
/Users/agent/strukto/mirage/mirage/runtime/remote.py
/Users/agent/strukto/mirage/mirage/runtime/local.py


find /Users/agent/strukto/mirage/mirage -type f -name "*.py" | sort


/Users/agent/strukto/mirage/mirage/__init__.py
/Users/agent/strukto/mirage/mirage/backend/__init__.py
/Users/agent/strukto/mirage/mirage/backend/base.py
/Users/agent/strukto/mirage/mirage/backend/filetype.py
/Users/agent/strukto/mirage/mirage/backend/loader.py
/Users/agent/strukto/mirage/mirage/backend/local/__init__.py
/Users/agent/strukto/mirage/mirage/backend/local/local.py
/Users/agent/strukto/mirage/mirage/backend/local/memory.py
/Users/agent/strukto/mirage/mirage/backend/network/__init__.py
/Users/agent/strukto/mirage/mirage/backend/network/http.py
/Users/agent/strukto/mirage/mirage/backend/network/proxy.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/__init__.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/r2.py
/Users/agent/strukto/mirage/mirage/backend/object_storage/s3.py
/Users/agent/strukto/mirage/mirage/cli/__init__.py
/Users/agent/strukto/mirage/mirage/cli/app.py
/Users/agent/strukto/mirage/mirage/cli/client.py
/Users/agent/strukto/mirage/mirage/cli/commands/__init__.py
/Users/agent/strukto/mirage/mirage/cli/commands/command.py
/Users/agent/strukto/mirage/mirage/cli/commands/observe.py
/Users/agent/strukto/mirage/mirage/cli/commands/server.py
/Users/agent/strukto/mirage/mirage/cli/commands/workspace.py
/Users/agent/strukto/mirage/mirage/cli/config.py
/Users/agent/strukto/mirage/mirage/cli/helpers.py
/Users/agent/strukto/mirage/mirage/commands/__init__.py
/Users/agent/strukto/mirage/mirage/commands/builtin/__init__.py
/Users/agent/strukto/mirage/mirage/commands/builtin/handlers.py
/Users/agent/strukto/mirage/mirage/commands/builtin/nav.py
/Users/agent/strukto/mirage/mirage/commands/builtin/net.py
/Users/agent/strukto/mirage/mirage/commands/builtin/read.py
/Users/agent/strukto/mirage/mirage/commands/builtin/search.py
/Users/agent/strukto/mirage/mirage/commands/builtin/write.py
/Users/agent/strukto/mirage/mirage/commands/custom/__init__.py
/Users/agent/strukto/mirage/mirage/commands/custom/arg_resolver.py
/Users/agent/strukto/mirage/mirage/commands/custom/executor.py
/Users/agent/strukto/mirage/mirage/commands/custom/registry.py
/Users/agent/strukto/mirage/mirage/commands/remote/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/auth.py
/Users/agent/strukto/mirage/mirage/commands/remote/client.py
/Users/agent/strukto/mirage/mirage/commands/remote/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/fastapi/app.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/__init__.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/command_pb2.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/proto/command_pb2_grpc.py
/Users/agent/strukto/mirage/mirage/commands/remote/grpc/server.py
/Users/agent/strukto/mirage/mirage/fuse/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/default.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/local.py
/Users/agent/strukto/mirage/mirage/fuse/backend/local/memory.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/backend/object_storage/s3.py
/Users/agent/strukto/mirage/mirage/fuse/base.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/csv.py
/Users/agent/strukto/mirage/mirage/fuse/filetype/parquet.py
/Users/agent/strukto/mirage/mirage/fuse/fs.py
/Users/agent/strukto/mirage/mirage/fuse/loader.py
/Users/agent/strukto/mirage/mirage/fuse/mount.py
/Users/agent/strukto/mirage/mirage/fuse/ops_log.py
/Users/agent/strukto/mirage/mirage/fuse/registry.py
/Users/agent/strukto/mirage/mirage/fuse/remote/__init__.py
/Users/agent/strukto/mirage/mirage/fuse/remote/remote.py
/Users/agent/strukto/mirage/mirage/observe/__init__.py
/Users/agent/strukto/mirage/mirage/observe/entries.py
/Users/agent/strukto/mirage/mirage/observe/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/observe/fastapi/router.py
/Users/agent/strukto/mirage/mirage/observe/inspector.py
/Users/agent/strukto/mirage/mirage/observe/session_log.py
/Users/agent/strukto/mirage/mirage/runtime/__init__.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/__init__.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/base.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/local.py
/Users/agent/strukto/mirage/mirage/runtime/accessors/remote.py
/Users/agent/strukto/mirage/mirage/runtime/base.py
/Users/agent/strukto/mirage/mirage/runtime/local.py
/Users/agent/strukto/mirage/mirage/runtime/memory.py
/Users/agent/strukto/mirage/mirage/runtime/remote.py
/Users/agent/strukto/mirage/mirage/server/__init__.py
/Users/agent/strukto/mirage/mirage/server/constants.py
/Users/agent/strukto/mirage/mirage/server/fastapi/__init__.py
/Users/agent/strukto/mirage/mirage/server/fastapi/app.py
/Users/agent/strukto/mirage/mirage/server/fastapi/config.py
/Users/agent/strukto/mirage/mirage/server/fastapi/daemon.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/__init__.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/command_router.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/io_router.py
/Users/agent/strukto/mirage/mirage/server/fastapi/routers/workspace_router.py
/Users/agent/strukto/mirage/mirage/server/routes.py
/Users/agent/strukto/mirage/mirage/server/service/__init__.py
/Users/agent/strukto/mirage/mirage/server/service/base.py
/Users/agent/strukto/mirage/mirage/server/service/cache.py
/Users/agent/strukto/mirage/mirage/server/service/local.py
/Users/agent/strukto/mirage/mirage/types.py
/Users/agent/strukto/mirage/mirage/workspace/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/cache/__init__.py
/Users/agent/strukto/mirage/mirage/workspace/cache/entry.py
/Users/agent/strukto/mirage/mirage/workspace/cache/store.py
/Users/agent/strukto/mirage/mirage/workspace/consistency.py
/Users/agent/strukto/mirage/mirage/workspace/mount.py
/Users/agent/strukto/mirage/mirage/workspace/sync.py
/Users/agent/strukto/mirage/mirage/workspace/workspace.py


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

<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.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 re
     2→
     3→from mirage.types import FileStat
     4→
     5→
     6→class BaseBackend:
     7→    """
     8→    Default command-level methods assembled from BackendOps primitives.
     9→    Backends override these for native efficiency (e.g. S3 uses list_objects_v2
    10→    for ls_l instead of per-file stat calls).
    11→    """
    12→
    13→    # ── ops that subclasses MUST implement ─────────────────────────────────
    14→
    15→    def read(self, path: str, offset: int, size: int) -> bytes:
    16→        raise NotImplementedError
    17→
    18→    def readdir(self,
    19→                path: str,
    20→                cursor: str | None = None) -> tuple[list[str], str | None]:
    21→        raise NotImplementedError
    22→
    23→    def stat(self, path: str) -> FileStat:
    24→        raise NotImplementedError
    25→
    26→    def open(self, path: str, flags: int) -> int:
    27→        raise NotImplementedError
    28→
    29→    def release(self, fh: int) -> None:
    30→        raise NotImplementedError
    31→
    32→    def write(self, path: str, offset: int, data: bytes) -> int:
    33→        raise NotImplementedError
    34→
    35→    def create(self, path: str) -> None:
    36→        raise NotImplementedError
    37→
    38→    def unlink(self, path: str) -> None:
    39→        raise NotImplementedError
    40→
    41→    def rmdir(self, path: str) -> None:
    42→        raise NotImplementedError
    43→
    44→    def mkdir(self, path: str) -> None:
    45→        raise NotImplementedError
    46→
    47→    def rename(self, src: str, dst: str) -> None:
    48→        raise NotImplementedError
    49→
    50→    def truncate(self, path: str, size: int) -> None:
    51→        raise NotImplementedError
    52→
    53→    # ── command-level helpers built from ops ────────────────────────────────
    54→
    55→    def exists(self, path: str) -> bool:
    56→        try:
    57→            self.stat(path)
    58→            return True
    59→        except (FileNotFoundError, KeyError):
    60→            return False
    61→
    62→    def cat(self, path: str) -> bytes:
    63→        s = self.stat(path)
    64→        size = s.size or 0
    65→        if size == 0:
    66→            return b""
    67→        return self.read(path, 0, size)
    68→
    69→    def ls(self, path: str) -> list[str]:
    70→        names: list[str] = []
    71→        cursor: str | None = None
    72→        while True:
    73→            batch, cursor = self.readdir(path, cursor)
    74→            names.extend(batch)
    75→            if cursor is None:
    76→                break
    77→        return names
    78→
    79→    def ls_l(self, path: str) -> list[FileStat]:
    80→        """Stat each entry. Backends override for batch efficiency."""
    81→        return [self.stat(name) for name in self.ls(path)]
    82→
    83→    def grep(self, pattern: str, path: str, flags: int = 0) -> list[str]:
    84→        """Full fetch + Python regex filter.
    85→
    86→        Backends override for native search."""
    87→        data = self.cat(path)
    88→        compiled = re.compile(pattern, flags)
    89→        return [
    90→            line for line in data.decode(errors="replace").splitlines()
    91→            if compiled.search(line)
    92→        ]
    93→
    94→    def head(self, path: str, lines: int = 10) -> bytes:
    95→        """Read first N lines via partial read (64KB chunk)."""
    96→        chunk_size = 64 * 1024
    97→        s = self.stat(path)
    98→        read_size = min(chunk_size, s.size or 0)
    99→        if read_size == 0:
   100→            return b""
   101→        data = self.read(path, 0, read_size)
   102→        parts = data.split(b"\n", lines)
   103→        return b"\n".join(parts[:lines])
   104→
   105→    def tail(self, path: str, lines: int = 10) -> bytes:
   106→        """Read last N lines via partial read (64KB chunk)."""
   107→        chunk_size = 64 * 1024
   108→        s = self.stat(path)
   109→        total = s.size or 0
   110→        if total == 0:
   111→            return b""
   112→        offset = max(0, total - chunk_size)
   113→        data = self.read(path, offset, total - offset)
   114→        parts = data.split(b"\n")
   115→        return b"\n".join(parts[-lines:])
   116→
   117→    def presign(self, path: str, expires: int = 3600) -> str:
   118→        raise NotImplementedError(
   119→            f"{type(self).__name__} does not support presigned URLs")
   120→
   121→    def put(self, path: str, data: bytes) -> None:
   122→        """Write full content to path (create if absent, replace if exists)."""
   123→        if not self.exists(path):
   124→            self.create(path)
   125→        self.truncate(path, 0)
   126→        if data:
   127→            self.write(path, 0, data)
   128→

<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 FileType
     2→
     3→EXTENSION_MAP: dict[str, FileType] = {
     4→    "json": FileType.JSON,
     5→    "jsonl": FileType.JSON,
     6→    "csv": FileType.CSV,
     7→    "tsv": FileType.CSV,
     8→    "txt": FileType.TEXT,
     9→    "md": FileType.TEXT,
    10→    "py": FileType.TEXT,
    11→    "js": FileType.TEXT,
    12→    "ts": FileType.TEXT,
    13→    "yaml": FileType.TEXT,
    14→    "yml": FileType.TEXT,
    15→    "toml": FileType.TEXT,
    16→    "png": FileType.IMAGE_PNG,
    17→    "jpg": FileType.IMAGE_PNG,
    18→    "jpeg": FileType.IMAGE_JPEG,
    19→    "gif": FileType.IMAGE_GIF,
    20→    "zip": FileType.ZIP,
    21→    "gz": FileType.GZIP,
    22→    "pdf": FileType.PDF,
    23→}
    24→
    25→DEFAULT_TYPE = FileType.BINARY
    26→
    27→
    28→def guess_type(path: str) -> FileType:
    29→    """Return the file type for *path* based on its extension.
    30→
    31→    Args:
    32→        path (str): file path or name.
    33→
    34→    Returns:
    35→        FileType: matched type from EXTENSION_MAP, or DEFAULT_TYPE.
    36→    """
    37→    ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
    38→    return EXTENSION_MAP.get(ext, DEFAULT_TYPE)
    39→

<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 importlib
     2→import importlib.util
     3→from typing import Any
     4→
     5→
     6→def load_backend_class(spec: str) -> type[Any]:
     7→    """Load a backend class from a spec string.
     8→
     9→    Supports two formats (auto-detected):
    10→    - Script file: ``"./my_backend.py:MyClass"``
    11→    - Module dotpath: ``"mypackage.backends:MyClass"``
    12→
    13→    Args:
    14→        spec (str): ``"source:class_name"`` where source is a file path
    15→            or module dotpath.
    16→
    17→    Returns:
    18→        type: The loaded class.
    19→    """
    20→    if ":" not in spec:
    21→        raise ValueError(
    22→            f"invalid backend spec {spec!r}, expected 'source:ClassName'")
    23→
    24→    source, class_name = spec.rsplit(":", 1)
    25→
    26→    if "/" in source or source.endswith(".py"):
    27→        module_spec = importlib.util.spec_from_file_location(
    28→            "_mirage_user_backend", source)
    29→        if module_spec is None or module_spec.loader is None:
    30→            raise ValueError(f"cannot load script {source!r}")
    31→        module = importlib.util.module_from_spec(module_spec)
    32→        try:
    33→            module_spec.loader.exec_module(module)
    34→        except FileNotFoundError:
    35→            raise ValueError(f"cannot load script {source!r}")
    36→    else:
    37→        module = importlib.import_module(source)
    38→
    39→    if not hasattr(module, class_name):
    40→        raise ValueError(f"{class_name!r} not found in {source!r}")
    41→
    42→    return getattr(module, class_name)
    43→

<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→from datetime import datetime, timezone
     3→from pathlib import Path
     4→
     5→from mirage.backend.base import BaseBackend
     6→from mirage.backend.filetype import guess_type
     7→from mirage.types import FileStat, FileType
     8→
     9→
    10→class LocalBackend(BaseBackend):
    11→    """
    12→    Backend backed by the local filesystem via pathlib/os.
    13→    No external dependencies.
    14→
    15→    All virtual paths are resolved relative to `root`.
    16→    Example: root="/data", path="/reports/q1.csv" → /data/reports/q1.csv
    17→    """
    18→
    19→    def __init__(self, root: str) -> None:
    20→        self.root = Path(root).resolve()
    21→
    22→    # ── helpers ─────────────────────────────────────────────────────────────
    23→
    24→    def _resolve(self, path: str) -> Path:
    25→        # strip leading slash so joinpath works correctly
    26→        relative = path.lstrip("/")
    27→        resolved = (self.root / relative).resolve()
    28→        # prevent path traversal outside root
    29→        resolved.relative_to(self.root)
    30→        return resolved
    31→
    32→    def _to_stat(self, p: Path) -> FileStat:
    33→        st = p.stat()
    34→        modified = datetime.fromtimestamp(st.st_mtime,
    35→                                          tz=timezone.utc).isoformat()
    36→        if p.is_dir():
    37→            return FileStat(name=p.name,
    38→                            size=None,
    39→                            modified=modified,
    40→                            type=FileType.DIRECTORY)
    41→        return FileStat(name=p.name,
    42→                        size=st.st_size,
    43→                        modified=modified,
    44→                        type=guess_type(p.name))
    45→
    46→    # ── ops ─────────────────────────────────────────────────────────────────
    47→
    48→    def read(self, path: str, offset: int, size: int) -> bytes:
    49→        p = self._resolve(path)
    50→        with p.open("rb") as f:
    51→            f.seek(offset)
    52→            return f.read(size)
    53→
    54→    def readdir(self,
    55→                path: str,
    56→                cursor: str | None = None) -> tuple[list[str], str | None]:
    57→        p = self._resolve(path)
    58→        if not p.is_dir():
    59→            raise NotADirectoryError(str(p))
    60→        base = "/" + path.strip("/")
    61→        entries = sorted(base.rstrip("/") + "/" + e.name for e in p.iterdir())
    62→        # cursor is an offset index for pagination
    63→        start = int(cursor) if cursor else 0
    64→        page = entries[start:]
    65→        return page, None
    66→
    67→    def stat(self, path: str) -> FileStat:
    68→        p = self._resolve(path)
    69→        if not p.exists():
    70→            raise FileNotFoundError(str(p))
    71→        return self._to_stat(p)
    72→
    73→    def open(self, path: str, flags: int) -> int:
    74→        raise NotImplementedError("open/release are FUSE-only")
    75→
    76→    def release(self, fh: int) -> None:
    77→        raise NotImplementedError("open/release are FUSE-only")
    78→
    79→    def write(self, path: str, offset: int, data: bytes) -> int:
    80→        p = self._resolve(path)
    81→        with p.open("r+b") as f:
    82→            f.seek(offset)
    83→            f.write(data)
    84→        return len(data)
    85→
    86→    def create(self, path: str) -> None:
    87→        p = self._resolve(path)
    88→        p.parent.mkdir(parents=True, exist_ok=True)
    89→        p.touch(exist_ok=True)
    90→
    91→    def unlink(self, path: str) -> None:
    92→        p = self._resolve(path)
    93→        p.unlink()
    94→
    95→    def rmdir(self, path: str) -> None:
    96→        p = self._resolve(path)
    97→        p.rmdir()  # raises OSError if non-empty — matches POSIX semantics
    98→
    99→    def mkdir(self, path: str) -> None:
   100→        p = self._resolve(path)
   101→        p.mkdir(parents=True, exist_ok=True)
   102→
   103→    def rename(self, src: str, dst: str) -> None:
   104→        s = self._resolve(src)
   105→        d = self._resolve(dst)
   106→        s.rename(d)
   107→
   108→    def truncate(self, path: str, size: int) -> None:
   109→        p = self._resolve(path)
   110→        os.truncate(p, size)
   111→

<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>