gabriel / muse public
feat patch dev-safety task/185-phase3-dev-guard #1 / 1
AI Agent gabriel · 2 days ago · Sep 10, 2026 · Diff

feat(dev-safety): Phase 3 of #185 — guard rail blocks mutating muse-dev calls on canonical repos

Adds muse/cli/dev_guard.py, wired into app.py's dispatch right before args.func(args): any editable build (muse-dev, or any future editable install — detected structurally via muse.__file__ not being under site-packages, not by binary name) refuses local-object-store-mutating commands (commit, merge, push, reset, rm, gc, prune, etc. — code add/reset/patch/rename/migrate/semantic-cherry-pick within the `code` namespace; grep/impact/hotspots/etc. remain unaffected) against a protected canonical repo (default: ~/ecosystem/muse, ~/ecosystem/musehub, overridable via MUSE_DEV_PROTECTED_ROOTS). Override via MUSE_DEV_ALLOW_CANONICAL=1, explicit and loud. A stable, non-editable muse build is never blocked, regardless of target — it has no live-source hazard.

Deliberately out of scope: `hub` subcommands (network calls to a remote server — different risk category, not local object-store corruption).

32 tests, written first (red before wiring into app.py, green after). Unit tests for the classifier functions never touch a real repo. The one true end-to-end integration test runs the real muse-dev binary but only ever against a disposable temp repo pointed to via MUSE_DEV_PROTECTED_ROOTS — never the real canonical repos this guard exists to protect.

Manually verified against the real ~/ecosystem/muse: `muse-dev -C ~/ecosystem/muse commit -m "..."` now exits 77 with a clear message and makes zero mutation (rev-parse dev unchanged before/after); read-only commands (status) still pass through unaffected; the stable `muse` build is untouched by any of this.

sha256:2a8dfcf895b528326eedf00a21babac1a18d90176b856ea1e0fb3027b39be0da sha
+49 ~1 symbols
3 changed · 1163 in snapshot files
sha256:c43cc05474295c3d53dee6b09d26e1ce17a96c50eea258f55c124975bae1b3dc snapshot
+49
symbols added
~1
symbol modified
3
files changed
1163
files in snapshot
0
dead code introduced
Semantic Changes 50 symbols
~ muse/cli/dev_guard.py .py 15 symbols added
+ DEFAULT_PROTECTED_ROOTS variable variable DEFAULT_PROTECTED_ROOTS L26–29
+ GuardBlocked class class GuardBlocked L51–52
+ OVERRIDE_ENV_VAR variable variable OVERRIDE_ENV_VAR L31–31
+ PROTECTED_ROOTS_ENV_VAR variable variable PROTECTED_ROOTS_ENV_VAR L32–32
+ _MUTATING_CODE_SUBCOMMANDS variable variable _MUTATING_CODE_SUBCOMMANDS L46–48
+ _MUTATING_TOP_LEVEL_COMMANDS variable variable _MUTATING_TOP_LEVEL_COMMANDS L36–42
+ _protected_roots_from_env function function _protected_roots_from_env L62–66
+ check_guard function function check_guard L88–124
+ classify_is_mutating class function classify_is_mutating L80–85
+ Path import import Path L24–24
+ annotations import import annotations L20–20
+ argparse import import argparse L22–22
+ os import import os L23–23
+ is_editable_build function function is_editable_build L55–59
+ is_protected_root function function is_protected_root L69–77
~ tests/test_dev_guard.py .py 34 symbols added
+ TestCheckGuard class class TestCheckGuard L70–94
+ test_mutating_command_against_protected_root_raises method method test_mutating_command_against_protected_root_raises L71–74
+ test_mutating_command_against_unprotected_root_passes method method test_mutating_command_against_unprotected_root_passes L84–88
+ test_mutating_command_with_override_env_passes method method test_mutating_command_with_override_env_passes L76–78
+ test_readonly_command_against_protected_root_passes method method test_readonly_command_against_protected_root_passes L80–82
+ test_stable_non_editable_build_is_never_blocked method method test_stable_non_editable_build_is_never_blocked L90–94
+ TestClassifyIsMutating class class TestClassifyIsMutating L48–67
+ test_code_add_is_mutating method method test_code_add_is_mutating L60–61
+ test_code_grep_is_readonly method method test_code_grep_is_readonly L57–58
+ test_code_impact_is_readonly method method test_code_impact_is_readonly L63–64
+ test_code_patch_is_mutating method method test_code_patch_is_mutating L66–67
+ test_known_mutating_top_level_commands method method test_known_mutating_top_level_commands L50–51
+ test_known_readonly_top_level_commands method method test_known_readonly_top_level_commands L54–55
+ TestIsProtectedRoot class class TestIsProtectedRoot L31–45
+ test_exact_match_is_protected method method test_exact_match_is_protected L32–33
+ test_sibling_path_is_not_protected method method test_sibling_path_is_not_protected L40–42
+ test_subdirectory_of_protected_root_is_protected method method test_subdirectory_of_protected_root_is_protected L35–38
+ test_unrelated_third_party_repo_is_not_protected method method test_unrelated_third_party_repo_is_not_protected L44–45
+ TestRealMuseDevBinaryIntegration class class TestRealMuseDevBinaryIntegration L97–158
+ _muse_dev_path method method _muse_dev_path L101–103
+ test_commit_allowed_with_override method method test_commit_allowed_with_override L126–143
+ test_commit_blocked_against_fake_protected_root method method test_commit_blocked_against_fake_protected_root L105–124
+ test_status_never_blocked_against_fake_protected_root method method test_status_never_blocked_against_fake_protected_root L145–158
+ _ns function function _ns L27–28
+ GuardBlocked import import GuardBlocked L19–19
+ Path import import Path L14–14
+ argparse import import argparse L10–10
+ check_guard import import check_guard L19–19
+ classify_is_mutating class import classify_is_mutating L19–19
+ is_protected_root import import is_protected_root L19–19
+ os import import os L11–11
+ pytest import import pytest L16–16
+ subprocess import import subprocess L12–12
+ sys import import sys L13–13
~ muse/cli/app.py .py 1 symbol modified
~ main
Files Changed
+2 ~1
1163 in snapshot
← Older Oldest on task/185-phase3-dev-guard
All commits
Newer → Latest on task/185-phase3-dev-guard

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:2a8dfcf895b528326eedf00a21babac1a18d90176b856ea1e0fb3027b39be0da --body "your comment"