v1_muse_reader.py
python
sha256:106ef9e12cca7f3be3ffb2a6bd3abfa76f032dce453c9e5010d732a8d86f2e14
Reconcile reviewed credential compatibility 32254cd939885d9…
Human
patch
8 days ago
| 1 | """Read-only bridge to Muse 0.2.1rc5, executed in its own conventional venv. |
| 2 | |
| 3 | Do not call the native CLI: require_repo performs GC and some stage readers |
| 4 | migrate files. Only object reads, ignore matching and hashing are reused here. |
| 5 | Linked worktree HEAD is resolved explicitly; native CLI discovery returns the |
| 6 | main store root. This reader never writes refs, caches, hooks or application files. |
| 7 | """ |
| 8 | from __future__ import annotations |
| 9 | |
| 10 | import hashlib |
| 11 | import importlib.metadata |
| 12 | import json |
| 13 | import os |
| 14 | from pathlib import Path |
| 15 | import re |
| 16 | import stat |
| 17 | import sys |
| 18 | import tomllib |
| 19 | |
| 20 | sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
| 21 | from cli.v1_io import Refusal, read |
| 22 | from cli.v1_policy import local_path |
| 23 | from cli.v1_revision import MUSE_VERSION, OID, unique_json |
| 24 | |
| 25 | |
| 26 | def readonly(event, args): |
| 27 | # A compatibility assertion for pinned Python APIs, not a hostile-code |
| 28 | # sandbox. Refuse unexpected writes/network/child processes on this path. |
| 29 | if event == "open" and args[2] & (os.O_WRONLY | os.O_RDWR | os.O_CREAT | os.O_TRUNC | os.O_APPEND): |
| 30 | raise Refusal("muse_reader_write_attempt") |
| 31 | if event in {"os.remove", "os.rename", "os.rmdir", "os.mkdir", "os.chmod", |
| 32 | "os.link", "os.symlink", "os.truncate", "subprocess.Popen", |
| 33 | "os.system", "socket.connect", "socket.bind"}: |
| 34 | raise Refusal("muse_reader_side_effect") |
| 35 | |
| 36 | |
| 37 | def document(root, path): |
| 38 | return json.loads(read(root, path), object_pairs_hook=unique_json) |
| 39 | |
| 40 | |
| 41 | def layout(root): |
| 42 | marker = root / ".muse" |
| 43 | if marker.is_symlink(): |
| 44 | raise Refusal("muse_marker_symlink") |
| 45 | if marker.is_dir(): |
| 46 | return root, ".muse/HEAD", None |
| 47 | pointer = read(root, ".muse").decode().strip() |
| 48 | if not pointer.startswith("musestore: "): |
| 49 | raise Refusal("muse_pointer_invalid") |
| 50 | path = Path(pointer[len("musestore: "):]) |
| 51 | if not path.is_absolute() or path.name != ".muse" or path.resolve() != path or not path.is_dir(): |
| 52 | raise Refusal("muse_pointer_invalid") |
| 53 | store = path.parent |
| 54 | matches = [] |
| 55 | for entry in (path / "worktrees").glob("*.json"): |
| 56 | data = document(store, str(entry.relative_to(store))) |
| 57 | if data.get("path") == str(root): |
| 58 | name = data.get("name") |
| 59 | if not isinstance(name, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+", name) or entry.stem != name: |
| 60 | raise Refusal("muse_worktree_invalid") |
| 61 | matches.append((".muse/worktrees/" + name + ".HEAD", data.get("branch"))) |
| 62 | if len(matches) != 1: |
| 63 | raise Refusal("muse_worktree_unregistered") |
| 64 | return store, *matches[0] |
| 65 | |
| 66 | |
| 67 | def run(root, with_dirty): |
| 68 | sys.addaudithook(readonly) |
| 69 | if sys.prefix == sys.base_prefix or importlib.metadata.version("muse") != MUSE_VERSION: |
| 70 | raise Refusal("muse_version_unsupported") |
| 71 | import muse |
| 72 | package = Path(muse.__file__).resolve().parent |
| 73 | if not package.is_relative_to(Path(sys.prefix).resolve()): |
| 74 | raise Refusal("muse_import_origin_mismatch") |
| 75 | source_hash = hashlib.sha256() |
| 76 | for path in sorted(package.rglob("*.py")): |
| 77 | source_hash.update(str(path.relative_to(package)).encode() + b"\0" + path.read_bytes() + b"\0") |
| 78 | from muse.core.commits import read_commit |
| 79 | from muse.core.snapshots import read_snapshot |
| 80 | from muse.core.validation import validate_branch_name |
| 81 | |
| 82 | store, head_path, registered_branch = layout(root) |
| 83 | identity = document(store, ".muse/repo.json") |
| 84 | if identity.get("domain") != "code" or not OID.fullmatch(identity.get("repo_id", "")): |
| 85 | raise Refusal("muse_identity_or_domain_unsupported") |
| 86 | head_data = read(store, head_path) |
| 87 | symbolic = head_data.decode().strip() |
| 88 | prefix = "ref: refs/heads/" if registered_branch is None else "refs/heads/" |
| 89 | if not symbolic.startswith(prefix): |
| 90 | raise Refusal("muse_detached_or_malformed_head") |
| 91 | branch = symbolic[len(prefix):] |
| 92 | validate_branch_name(branch) |
| 93 | if registered_branch is not None and registered_branch != branch: |
| 94 | raise Refusal("muse_worktree_branch_mismatch") |
| 95 | ref_path = ".muse/refs/heads/" + branch |
| 96 | ref = read(store, ref_path, missing=True) |
| 97 | head = "unborn" if ref in (None, b"") else ref.decode().strip() |
| 98 | if head != "unborn" and not OID.fullmatch(head): |
| 99 | raise Refusal("muse_ref_invalid") |
| 100 | for name in ("CHECKOUT_HEAD", "MERGE_STATE.json"): |
| 101 | if (store / ".muse" / name).exists(): |
| 102 | raise Refusal("muse_checkout_or_merge_in_progress") |
| 103 | manifest = {} |
| 104 | directories = [] |
| 105 | if head != "unborn": |
| 106 | commit = read_commit(store, head) |
| 107 | snapshot = read_snapshot(store, commit.snapshot_id) if commit else None |
| 108 | if snapshot is None: |
| 109 | raise Refusal("muse_revision_objects_missing_or_corrupt") |
| 110 | manifest = snapshot.manifest |
| 111 | directories = snapshot.directories |
| 112 | for path, oid in manifest.items(): |
| 113 | if Path(path).is_absolute() or ".." in Path(path).parts or not OID.fullmatch(oid): |
| 114 | raise Refusal("muse_manifest_invalid") |
| 115 | # Never call read_stage: its legacy-format path deletes the old index. |
| 116 | stage_raw = read(store, ".muse/code/stage.json", missing=True) |
| 117 | stage = json.loads(stage_raw, object_pairs_hook=unique_json) if stage_raw else {"entries": {}} |
| 118 | if not isinstance(stage, dict) or not isinstance(stage.get("entries"), dict): |
| 119 | raise Refusal("muse_stage_invalid") |
| 120 | tracked = sorted(p for p in set(manifest) | set(stage["entries"]) if local_path(p)) |
| 121 | dirty = None |
| 122 | if with_dirty: |
| 123 | from muse.core.ignore import is_ignored, resolve_patterns |
| 124 | from muse.core.snapshot import _BUILTIN_SECRET_PATTERNS |
| 125 | from muse.core.types import hash_file |
| 126 | from muse.plugins.code.plugin import _ALWAYS_IGNORE_DIRS |
| 127 | |
| 128 | raw_ignore = read(root, ".museignore", missing=True) |
| 129 | ignore = tomllib.loads(raw_ignore.decode()) if raw_ignore else {} |
| 130 | patterns = _BUILTIN_SECRET_PATTERNS + resolve_patterns(ignore, "code") |
| 131 | current = {} |
| 132 | for directory, dirs, files in os.walk(root, followlinks=False): |
| 133 | here = Path(directory) |
| 134 | rel = here.relative_to(root) |
| 135 | dirs[:] = [d for d in dirs if d not in _ALWAYS_IGNORE_DIRS |
| 136 | and not (here / d).is_symlink() and not (here / d / ".muse").exists() |
| 137 | and not is_ignored((rel / d / "_").as_posix(), patterns)] |
| 138 | for name in files: |
| 139 | if name in (".muse", ".git"): |
| 140 | continue |
| 141 | path = here / name |
| 142 | relative = path.relative_to(root).as_posix() |
| 143 | if is_ignored(relative, patterns): |
| 144 | continue |
| 145 | if stat.S_ISREG(path.lstat().st_mode): |
| 146 | current[relative] = hash_file(path) |
| 147 | # Staging is shared by this Muse version. It is reported dirty in any |
| 148 | # linked checkout too; do not mistake the primary index for a clean lane. |
| 149 | dirty = bool(stage["entries"]) or current != manifest or any( |
| 150 | not (root / d).is_dir() for d in directories) |
| 151 | if read(store, head_path) != head_data or read(store, ref_path, missing=True) != ref: |
| 152 | raise Refusal("muse_revision_changed_during_read") |
| 153 | return {"root": str(root), "store": str(store), "repo_id": identity["repo_id"], |
| 154 | "branch": branch, "head": head, "dirty": dirty, "tracked_local": tracked, |
| 155 | "runtime": {"python": str(Path(sys.prefix).resolve() / "bin" / Path(sys.executable).name), |
| 156 | "version": MUSE_VERSION, "sha256": source_hash.hexdigest()}} |
| 157 | |
| 158 | |
| 159 | if __name__ == "__main__": |
| 160 | try: |
| 161 | print(json.dumps(run(Path(sys.argv[1]), sys.argv[2] == "dirty"))) |
| 162 | except Exception as exc: |
| 163 | print("muse reader refused: " + (str(exc) if isinstance(exc, Refusal) else type(exc).__name__), file=sys.stderr) |
| 164 | raise SystemExit(2) |
File History
2 commits
sha256:106ef9e12cca7f3be3ffb2a6bd3abfa76f032dce453c9e5010d732a8d86f2e14
Reconcile reviewed credential compatibility 32254cd939885d9…
Human
patch
8 days ago
sha256:c87cf2c2f03da507f66f93922cb339a2cbebb33ad6372a6039032fb09a20da12
Reconcile reviewed Overseer recovery d47291d5d9030de5ebef71…
Human
minor
⚠
8 days ago