#!/usr/bin/env python3 """Git workspace isolation, scope fingerprinting, and patch transport.""" from __future__ import annotations import contextlib import hashlib import mimetypes import os import subprocess from collections.abc import Mapping, Sequence from pathlib import Path from typing import Any from mmo_util import atomic_write_bytes class WorkspaceTargetNotGit(RuntimeError): """A writable worker target has no Git repository to isolate.""" def _git( cwd: Path, *args: str, env: Mapping[str, str] | None = None ) -> subprocess.CompletedProcess[bytes]: process_env = os.environ.copy() if env: process_env.update(env) return subprocess.run( ["git", "-C", str(cwd), *args], stdin=subprocess.DEVNULL, capture_output=True, check=False, env=process_env, ) def _git_root(cwd: Path) -> Path | None: result = _git(cwd, "rev-parse", "--show-toplevel") if result.returncode != 0: return None root = Path(os.fsdecode(result.stdout.removesuffix(b"\n"))).resolve() return root if root.is_dir() else None def _content_fingerprint(path: Path) -> str: digest = hashlib.sha256() try: status = path.lstat() except FileNotFoundError: return "missing" digest.update(f"{status.st_mode:o}\0{status.st_size}\0".encode()) if path.is_symlink(): digest.update(os.fsencode(os.readlink(path))) elif path.is_file(): with path.open("rb") as handle: while chunk := handle.read(1024 * 1024): digest.update(chunk) return digest.hexdigest() def _scope_fingerprints(repo_root: Path, cwd: Path, scopes: Sequence[str]) -> dict[str, Any]: repo_scopes: list[str] = [] for scope in scopes: absolute = (cwd / scope).resolve(strict=False) try: repo_scopes.append(absolute.relative_to(repo_root).as_posix()) except ValueError as exc: raise ValueError(f"write scope escapes Git repository: {scope}") from exc listed = _git( repo_root, "ls-files", "-co", "--exclude-standard", "-z", "--", *repo_scopes, ) if listed.returncode != 0: raise RuntimeError( "unable to fingerprint write scope: " + listed.stderr.decode("utf-8", errors="replace")[-2000:] ) paths = sorted( {os.fsdecode(value) for value in listed.stdout.split(b"\0") if value} | set(repo_scopes) ) return { "repo_root": str(repo_root), "scope_roots": repo_scopes, "paths": {relative: _content_fingerprint(repo_root / relative) for relative in paths}, } def create_isolated_worktree( canonical_cwd: Path, directory: Path, scopes: Sequence[str], attachments: Sequence[str], ) -> dict[str, Any]: repo_root = _git_root(canonical_cwd) if repo_root is None: raise WorkspaceTargetNotGit("writable MCP workers require a Git worktree target") try: relative_cwd = canonical_cwd.relative_to(repo_root) except ValueError as exc: raise RuntimeError("job cwd is outside its reported Git root") from exc index_path = directory / "synthetic.index" index_env = {"GIT_INDEX_FILE": str(index_path)} head = _git(repo_root, "rev-parse", "--verify", "HEAD") if head.returncode == 0: read_tree = _git(repo_root, "read-tree", "HEAD", env=index_env) else: read_tree = _git(repo_root, "read-tree", "--empty", env=index_env) if read_tree.returncode != 0: raise RuntimeError("unable to initialize synthetic Git index") add = _git(repo_root, "add", "-A", "--", ".", env=index_env) if add.returncode != 0: raise RuntimeError( "unable to snapshot Git workspace: " + add.stderr.decode("utf-8", errors="replace")[-2000:] ) tree = _git(repo_root, "write-tree", env=index_env) if tree.returncode != 0: raise RuntimeError("unable to write synthetic Git tree") # ``git commit-tree`` reads its log message from stdin unless one is # supplied. MMO normally runs beneath an interactive Codex TUI, so # inheriting stdin here can block a writable worker forever waiting for # operator input. Keep the synthetic snapshot non-interactive and # deterministic. commit_args = [ "commit-tree", tree.stdout.decode("ascii").strip(), "-m", "Codex MMO isolated workspace snapshot", ] if head.returncode == 0: commit_args.extend(["-p", head.stdout.decode("ascii").strip()]) commit = _git( repo_root, *commit_args, env={ "GIT_AUTHOR_NAME": "Codex MMO", "GIT_AUTHOR_EMAIL": "codex-mmo@localhost", "GIT_COMMITTER_NAME": "Codex MMO", "GIT_COMMITTER_EMAIL": "codex-mmo@localhost", }, ) with contextlib.suppress(OSError): index_path.unlink() if commit.returncode != 0: raise RuntimeError( "unable to create synthetic Git commit: " + commit.stderr.decode("utf-8", errors="replace")[-2000:] ) base_commit = commit.stdout.decode("ascii").strip() worktree_root = directory / "worktree" added = _git(repo_root, "worktree", "add", "--detach", str(worktree_root), base_commit) if added.returncode != 0: raise RuntimeError( "unable to create isolated Git worktree: " + added.stderr.decode("utf-8", errors="replace")[-2000:] ) execution_cwd = (worktree_root / relative_cwd).resolve() mapped_attachments: list[str] = [] for raw in attachments: canonical = Path(raw).resolve() mapped_attachments.append(str(worktree_root / canonical.relative_to(repo_root))) return { "canonical_cwd": str(canonical_cwd), "canonical_repo_root": str(repo_root), "worktree_root": str(worktree_root), "cwd": execution_cwd, "attachments": mapped_attachments, "base_commit": base_commit, "base_fingerprints": _scope_fingerprints(repo_root, canonical_cwd, scopes), } def remove_isolated_worktree(metadata: Mapping[str, Any]) -> None: root_value = metadata.get("canonical_repo_root") worktree_value = metadata.get("worktree_root") if not isinstance(root_value, str) or not isinstance(worktree_value, str): return _git(Path(root_value), "worktree", "remove", "--force", worktree_value) def _nul_paths(result: subprocess.CompletedProcess[bytes]) -> set[str]: if result.returncode != 0: return set() return {os.fsdecode(value) for value in result.stdout.split(b"\0") if value} def _relative_to_job_cwd(repo_root: Path, job_cwd: Path, repo_relative: str) -> str | None: absolute = (repo_root / repo_relative).resolve(strict=False) try: return absolute.relative_to(job_cwd).as_posix() except ValueError: return None def path_within_scope(relative: str, scopes: list[str]) -> bool: path = Path(relative) for raw in scopes: scope = Path(raw) if raw == "." or path == scope: return True with contextlib.suppress(ValueError): path.relative_to(scope) return True return False def capture_isolated_patch( metadata: dict[str, Any], directory: Path, cwd: Path ) -> tuple[list[str], list[dict[str, Any]], dict[str, Any] | None]: """Capture one scope-checked binary patch from a disposable worker worktree.""" worktree_value = metadata.get("worktree_root") base_commit = metadata.get("base_commit") if not isinstance(worktree_value, str) or not isinstance(base_commit, str): return ["writable worker lacks isolated worktree metadata"], [], None worktree_root = Path(worktree_value) staged = _git(worktree_root, "add", "-A", "--", ".") if staged.returncode != 0: return ["unable to stage isolated worker changes"], [], None names = _git( worktree_root, "diff", "--cached", "--no-renames", "--name-only", "-z", base_commit, "--", ) if names.returncode != 0: return ["unable to enumerate isolated worker changes"], [], None changed = sorted(_nul_paths(names)) violations: list[str] = [] relative_changes: list[tuple[str, str]] = [] scopes = list(metadata.get("write_scope", [])) for repo_relative in changed: relative = _relative_to_job_cwd(worktree_root, cwd, repo_relative) if relative is None or not path_within_scope(relative, scopes): violations.append(repo_relative) else: relative_changes.append((repo_relative, relative)) if violations: return ["out-of-scope mutation: " + ", ".join(violations[:40])], [], None patch_result = _git( worktree_root, "diff", "--cached", "--binary", "--full-index", "--no-renames", base_commit, "--", ) if patch_result.returncode != 0: return ["unable to produce isolated binary patch"], [], None patch_path = directory / "changes.patch" atomic_write_bytes(patch_path, patch_result.stdout, 0o600) artifacts: list[dict[str, Any]] = [] for repo_relative, relative in relative_changes: path = worktree_root / repo_relative if not path.is_file(): artifacts.append( { "relative_path": relative, "sha256": hashlib.sha256(b"").hexdigest(), "size": 0, "media_type": "application/x-deleted", "state": "deleted", } ) continue content = path.read_bytes() media_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream" artifacts.append( { "relative_path": relative, "sha256": hashlib.sha256(content).hexdigest(), "size": len(content), "media_type": media_type, "state": "present", } ) patch = { "path": str(patch_path), "sha256": hashlib.sha256(patch_result.stdout).hexdigest(), "size": len(patch_result.stdout), "base_commit": base_commit, "base_fingerprints": metadata.get("base_fingerprints"), "changed_paths": [relative for _repo, relative in relative_changes], } return [], artifacts, patch def apply_validated_patch( *, canonical_cwd: Path, repo_root: Path, scopes: Sequence[str], base_fingerprints: Any, patch_path: Path, ) -> None: """Verify a worker snapshot boundary and apply its already-authenticated patch.""" if _git_root(canonical_cwd) != repo_root: raise RuntimeError("canonical Git repository identity changed before integration") current = _scope_fingerprints(repo_root, canonical_cwd, scopes) if current != base_fingerprints: raise RuntimeError( "canonical write scope changed after worker snapshot; integration refused" ) check = _git(repo_root, "apply", "--check", "--binary", str(patch_path)) if check.returncode != 0: raise RuntimeError( "git apply --check rejected worker patch: " + check.stderr.decode("utf-8", errors="replace")[-2000:] ) applied = _git(repo_root, "apply", "--binary", str(patch_path)) if applied.returncode != 0: raise RuntimeError( "worker patch integration failed: " + applied.stderr.decode("utf-8", errors="replace")[-2000:] ) def reverse_applied_patch(repo_root: Path, patch_path: Path) -> None: """Reverse one patch after its lifecycle publication fails.""" rollback = _git(repo_root, "apply", "--reverse", "--binary", str(patch_path)) if rollback.returncode != 0: raise RuntimeError(rollback.stderr.decode("utf-8", errors="replace")[-2000:])