fix(python): mirror official Grok hooks

This commit is contained in:
ZacharyZhang-NY committed 2026-07-20 05:44:34 -04:00
1 parent d84580e5a7
commit ed36743d77
16 files changed
+2174 -784

No files matched your search

+246
View File
@@ -0,0 +1,246 @@
from __future__ import annotations
from dataclasses import dataclass
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import os
from pathlib import Path
import shlex
import subprocess # nosec B404
import sys
from threading import Thread
from typing import Any, Mapping
import pytest
from elydora.plugins.grok import GrokPlugin
from elydora.utils import base64url_encode
AGENT_ID = "agent-1"
MISSING = object()
VALID_PRIVATE_KEY = base64url_encode(bytes(range(32)))
@dataclass
class GrokFixture:
root_dir: Path
home_dir: Path
project_dir: Path
grok_home: Path
config_path: Path
agent_dir: Path
guard_path: Path
hook_path: Path
runtime_config_path: Path
private_key_path: Path
plugin: GrokPlugin
config: dict[str, str]
def install(self) -> None:
self.plugin.install(self.config)
def settings(self) -> dict[str, Any]:
return json.loads(self.config_path.read_text(encoding="utf-8"))
class GrokApiHandler(BaseHTTPRequestHandler):
agent_status: Any = "active"
operation_status = 201
requests: list[dict[str, Any]] = []
@classmethod
def reset(cls) -> None:
cls.agent_status = "active"
cls.operation_status = 201
cls.requests = []
def _respond(self, status: int, value: object) -> None:
body = json.dumps(value).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None:
type(self).requests.append({
"method": "GET",
"path": self.path,
"authorization": self.headers.get("Authorization"),
})
self._respond(200, {"agent": {"status": type(self).agent_status}})
def do_POST(self) -> None:
length = int(self.headers.get("Content-Length", "0"))
raw = self.rfile.read(length)
type(self).requests.append({
"method": "POST",
"path": self.path,
"authorization": self.headers.get("Authorization"),
"raw": raw,
"json": json.loads(raw),
})
status = type(self).operation_status
response: object = (
{"operation": {"accepted": True}}
if 200 <= status < 300
else {"error": {"code": "UPSTREAM_FAILURE", "message": "failed"}}
)
self._respond(status, response)
def log_message(self, _format: str, *_args: object) -> None:
return
def start_api_server(
*, status: str = "active", operation_status: int = 201
) -> ThreadingHTTPServer:
GrokApiHandler.reset()
GrokApiHandler.agent_status = status
GrokApiHandler.operation_status = operation_status
server = ThreadingHTTPServer(("127.0.0.1", 0), GrokApiHandler)
Thread(target=server.serve_forever, daemon=True).start()
return server
def server_base_url(server: ThreadingHTTPServer) -> str:
return f"http://127.0.0.1:{server.server_port}"
def write_text(path: Path, source: str, mode: int = 0o600) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(source, encoding="utf-8")
os.chmod(path, mode)
def prepare_fixture(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
*,
existing_config: object = MISSING,
explicit_grok_home: bool = True,
base_url: str = "http://127.0.0.1:9",
) -> GrokFixture:
home_dir = tmp_path / "home with spaces and 'quote %GROK_HOOK_EVENT%"
project_dir = tmp_path / "project with spaces"
grok_home = (
home_dir / "custom grok"
if explicit_grok_home
else home_dir / ".grok"
)
config_path = grok_home / "hooks" / "elydora-audit.json"
agent_dir = home_dir / ".elydora" / AGENT_ID
guard_path = agent_dir / "guard.py"
hook_path = agent_dir / "hook.py"
project_dir.mkdir(parents=True)
if existing_config is not MISSING:
source = (
str(existing_config)
if isinstance(existing_config, str)
else json.dumps(existing_config, indent=2) + "\n"
)
write_text(config_path, source)
monkeypatch.setenv("HOME", str(home_dir))
monkeypatch.setenv("USERPROFILE", str(home_dir))
monkeypatch.setenv("GROK_HOOK_EVENT", "injected-command-fragment")
if explicit_grok_home:
monkeypatch.setenv("GROK_HOME", str(grok_home))
else:
monkeypatch.delenv("GROK_HOME", raising=False)
return GrokFixture(
root_dir=tmp_path,
home_dir=home_dir,
project_dir=project_dir,
grok_home=grok_home,
config_path=config_path,
agent_dir=agent_dir,
guard_path=guard_path,
hook_path=hook_path,
runtime_config_path=agent_dir / "config.json",
private_key_path=agent_dir / "private.key",
plugin=GrokPlugin(),
config={
"org_id": "org-1",
"agent_id": AGENT_ID,
"agent_name": "grok",
"private_key": VALID_PRIVATE_KEY,
"kid": "kid-1",
"token": "token-1",
"base_url": base_url,
"guard_script_path": str(guard_path),
},
)
def managed_handler(settings: Mapping[str, Any], event: str) -> dict[str, Any]:
for group in reversed(settings.get("hooks", {}).get(event, [])):
if set(group) != {"hooks"} or not isinstance(group["hooks"], list):
continue
for handler in reversed(group["hooks"]):
if (
set(handler) == {"type", "command", "timeout"}
and handler.get("type") == "command"
and handler.get("timeout") == 10
):
return handler
raise AssertionError(f"managed {event} handler not found")
def assert_managed_handler(handler: Mapping[str, Any]) -> None:
assert set(handler) == {"type", "command", "timeout"}
assert handler["type"] == "command"
assert handler["timeout"] == 10
command = str(handler["command"])
if os.name == "nt":
assert "powershell.exe" in command.lower()
assert " -EncodedCommand " in command
else:
assert command.startswith("'")
def assert_managed_triple(settings: Mapping[str, Any]) -> None:
for event in ("PreToolUse", "PostToolUse", "PostToolUseFailure"):
assert_managed_handler(managed_handler(settings, event))
assert managed_handler(settings, "PostToolUse")["command"] == managed_handler(
settings, "PostToolUseFailure"
)["command"]
def run_hook(
command: str,
payload: str,
fixture: GrokFixture,
environment: Mapping[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
command,
shell=True,
input=payload,
capture_output=True,
check=False,
cwd=fixture.project_dir,
env={
**os.environ,
"HOME": str(fixture.home_dir),
"USERPROFILE": str(fixture.home_dir),
**dict(environment or {}),
},
text=True,
)
def legacy_command(script_path: Path) -> str:
arguments = [sys.executable, str(script_path)]
return (
subprocess.list2cmdline(arguments)
if os.name == "nt"
else shlex.join(arguments)
)
def assert_no_transaction_files(root: Path) -> None:
assert not list(root.rglob("*.tmp"))
assert not list(root.rglob("*.rollback"))
+206 -369
View File
@@ -1,141 +1,33 @@
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
from typing import Any
import pytest
from elydora import cli
from elydora.plugins import grok
from elydora.plugins.base import InstallConfig
from elydora.plugins.registry import SUPPORTED_AGENTS
AGENT_ID = "agent-1"
MISSING = object()
@dataclass(frozen=True)
class GrokFixture:
plugin: grok.GrokPlugin
config: InstallConfig
home_dir: Path
grok_home: Path
agent_dir: Path
config_path: Path
guard_path: Path
hook_path: Path
runtime_config_path: Path
private_key_path: Path
def prepare_fixture(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
*,
existing_config: object = MISSING,
explicit_grok_home: bool = True,
create_guard: bool = True,
) -> GrokFixture:
home_dir = tmp_path / "home with spaces"
agent_dir = home_dir / ".elydora" / AGENT_ID
grok_home = (
home_dir / "custom grok"
if explicit_grok_home
else home_dir / ".grok"
)
config_path = grok_home / "hooks" / "elydora-audit.json"
guard_path = agent_dir / "guard.py"
hook_path = agent_dir / "hook.py"
agent_dir.mkdir(parents=True)
if create_guard:
guard_path.write_text(
"import sys\nsys.stdin.read()\n"
"sys.stderr.write('Agent is frozen by Elydora.')\n"
"raise SystemExit(2)\n",
encoding="utf-8",
)
if existing_config is not MISSING:
config_path.parent.mkdir(parents=True, exist_ok=True)
raw = (
str(existing_config)
if isinstance(existing_config, str)
else json.dumps(existing_config, indent=2)
)
config_path.write_text(raw, encoding="utf-8")
monkeypatch.setattr(grok, "ELYDORA_DIR", str(agent_dir.parent))
monkeypatch.setattr(grok, "_home_dir", lambda: str(home_dir))
if explicit_grok_home:
monkeypatch.setenv("GROK_HOME", str(grok_home))
else:
monkeypatch.delenv("GROK_HOME", raising=False)
config: InstallConfig = {
"agent_id": AGENT_ID,
"agent_name": "grok",
"org_id": "org-1",
"private_key": "test-key",
"kid": "kid-1",
"base_url": "https://api.elydora.test",
"guard_script_path": str(guard_path),
}
return GrokFixture(
plugin=grok.GrokPlugin(),
config=config,
home_dir=home_dir,
grok_home=grok_home,
agent_dir=agent_dir,
config_path=config_path,
guard_path=guard_path,
hook_path=hook_path,
runtime_config_path=agent_dir / "config.json",
private_key_path=agent_dir / "private.key",
)
def managed_handler(
settings: dict[str, Any], event: str, script_name: str
) -> dict[str, Any]:
for group in settings["hooks"][event]:
if "matcher" in group:
continue
for handler in group["hooks"]:
if script_name in str(handler.get("command")):
return handler
raise AssertionError(f"managed {event} handler not found")
def run_command(
command: str,
home_dir: Path,
payload: str,
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
command,
shell=True,
capture_output=True,
check=False,
env={**os.environ, "HOME": str(home_dir), "USERPROFILE": str(home_dir)},
input=payload,
text=True,
)
from grok_support import (
AGENT_ID,
assert_managed_triple,
assert_no_transaction_files,
legacy_command,
prepare_fixture,
)
def test_grok_is_registered_in_sdk_and_cli() -> None:
assert SUPPORTED_AGENTS["grok"] == {
"name": "Grok Build",
"hook_event": "PreToolUse/PostToolUse",
"hook_event": "PreToolUse/PostToolUse/PostToolUseFailure",
"config_path": "~/.grok/hooks/elydora-audit.json",
}
assert cli.PLUGIN_MAP["grok"] is grok.GrokPlugin
def test_install_preserves_native_config_and_is_idempotent(
def test_install_writes_exact_triple_and_preserves_valid_user_hooks(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
@@ -144,17 +36,15 @@ def test_install_preserves_native_config_and_is_idempotent(
"schemaVersion": 1,
"hooks": {
"SessionStart": [{
"matcher": "startup",
"hooks": [{
"type": "http",
"url": "https://example.test/hook",
"timeout": 5,
"headers": {"x": "keep"},
"timeout": 0,
}],
"label": "keep group metadata",
}],
"PreToolUse": [{
"matcher": "Bash",
"matcher": "Bash|run_terminal_command",
"hooks": [{
"type": "command",
"command": "existing-command",
@@ -163,36 +53,25 @@ def test_install_preserves_native_config_and_is_idempotent(
}],
},
}
fixture = prepare_fixture(
monkeypatch, tmp_path, existing_config=existing
)
fixture = prepare_fixture(monkeypatch, tmp_path, existing_config=existing)
fixture.plugin.install(fixture.config)
fixture.plugin.install(fixture.config)
fixture.install()
first_source = fixture.config_path.read_text(encoding="utf-8")
fixture.install()
assert "global PreToolUse and PostToolUse hooks installed" in capsys.readouterr().out
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert "PostToolUseFailure hooks installed" in capsys.readouterr().out
settings = fixture.settings()
assert fixture.config_path.read_text(encoding="utf-8") == first_source
assert settings["schemaVersion"] == 1
assert settings["hooks"]["SessionStart"] == existing["hooks"]["SessionStart"]
assert settings["hooks"]["PreToolUse"][0] == existing["hooks"]["PreToolUse"][0]
assert len(settings["hooks"]["PreToolUse"]) == 2
assert len(settings["hooks"]["PostToolUse"]) == 1
assert set(settings["hooks"]["PreToolUse"][1]) == {"hooks"}
for event, script_name in (
("PreToolUse", "guard.py"),
("PostToolUse", "hook.py"),
):
handler = managed_handler(settings, event, script_name)
assert set(handler) == {"type", "command", "timeout"}
assert handler["type"] == "command"
assert handler["timeout"] == 10
assert json.loads(fixture.runtime_config_path.read_text())["agent_name"] == "grok"
assert fixture.private_key_path.read_text(encoding="utf-8") == "test-key"
assert (fixture.home_dir / ".claude" / "settings.json").exists() is False
assert (fixture.home_dir / ".cursor" / "hooks.json").exists() is False
assert (
assert_managed_triple(settings)
assert not (fixture.home_dir / ".claude" / "settings.json").exists()
assert not (fixture.home_dir / ".cursor" / "hooks.json").exists()
assert not (
fixture.home_dir / ".grok" / "hooks" / "elydora-audit.json"
).exists() is False
).exists()
def test_empty_home_override_uses_official_default(
@@ -201,271 +80,229 @@ def test_empty_home_override_uses_official_default(
fixture = prepare_fixture(
monkeypatch, tmp_path, explicit_grok_home=False
)
fixture.plugin.install(fixture.config)
fixture.install()
monkeypatch.setenv("GROK_HOME", "")
assert fixture.plugin.status()["installed"] is True
assert_managed_triple(fixture.settings())
def test_commands_block_and_forward_official_payload_byte_for_byte(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
capture_path = tmp_path / "captured-event.json"
fixture.hook_path.write_text(
"from pathlib import Path\nimport sys\n"
f"Path({str(capture_path)!r}).write_text(sys.stdin.read(), encoding='utf-8')\n",
encoding="utf-8",
)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
pre_payload = json.dumps({
"hookEventName": "PreToolUse",
"sessionId": "session-1",
"cwd": str(fixture.home_dir),
"workspaceRoot": str(fixture.home_dir),
"toolName": "Bash",
"toolInput": {"command": "echo test"},
}, separators=(",", ":"))
guard = managed_handler(settings, "PreToolUse", "guard.py")
guard_result = run_command(guard["command"], fixture.home_dir, pre_payload)
assert guard_result.returncode == 2
assert "Agent is frozen by Elydora" in guard_result.stderr
post_payload = json.dumps({
"hookEventName": "PostToolUse",
"sessionId": "session-1",
"cwd": str(fixture.home_dir),
"workspaceRoot": str(fixture.home_dir),
"toolName": "Bash",
"toolInput": {"command": "echo test"},
"toolResult": {"output": "test"},
}, separators=(",", ":"))
audit = managed_handler(settings, "PostToolUse", "hook.py")
audit_result = run_command(audit["command"], fixture.home_dir, post_payload)
assert audit_result.returncode == 0
assert capture_path.read_text(encoding="utf-8") == post_payload
def test_status_requires_complete_pair_and_both_runtime_files(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
assert fixture.plugin.status()["installed"] is True
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
del settings["hooks"]["PostToolUse"]
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
assert fixture.plugin.status()["installed"] is False
fixture.plugin.install(fixture.config)
fixture.guard_path.unlink()
status = fixture.plugin.status()
assert status["installed"] is False
assert "runtime scripts missing" in status["details"]
def test_uninstall_removes_exact_ownership_and_preserves_mixed_handlers(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_config={"owner": "user", "hooks": {"Notification": []}},
)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
settings["hooks"]["PreToolUse"][-1]["hooks"].append({
"type": "command",
"command": "user-command",
"timeout": 10,
})
settings["hooks"]["PreToolUse"].append({"hooks": [{
"type": "command",
"command": grok._build_command(str(fixture.agent_dir / "guard.py.backup")),
"timeout": 10,
}]})
settings["hooks"]["PreToolUse"].append({"hooks": [{
"type": "command",
"command": grok._build_command(str(
fixture.agent_dir.parent / "agent-10" / "guard.py"
)),
"timeout": 10,
}]})
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
uninstall_id = "AGENT-1" if os.name == "nt" else AGENT_ID
fixture.plugin.uninstall(uninstall_id)
remaining = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert remaining["owner"] == "user"
assert remaining["hooks"]["Notification"] == []
assert len(remaining["hooks"]["PreToolUse"]) == 3
assert remaining["hooks"]["PreToolUse"][0]["hooks"] == [{
"type": "command",
"command": "user-command",
"timeout": 10,
}]
raw = fixture.config_path.read_text(encoding="utf-8")
assert "guard.py.backup" in raw
assert "agent-10" in raw
assert "PostToolUse" not in remaining["hooks"]
def test_install_replaces_stale_handlers_for_every_agent(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
for event, script_name in (
("PreToolUse", "guard.py"),
("PostToolUse", "hook.py"),
):
settings["hooks"][event].append({"hooks": [{
"type": "command",
"command": grok._build_command(str(
fixture.agent_dir.parent / "agent-old" / script_name
)),
"timeout": 10,
}]})
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
fixture.plugin.install(fixture.config)
raw = fixture.config_path.read_text(encoding="utf-8")
current = json.loads(raw)
assert "agent-old" not in raw
assert len(current["hooks"]["PreToolUse"]) == 1
assert len(current["hooks"]["PostToolUse"]) == 1
def test_uninstall_preserves_untouched_empty_native_group(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch, tmp_path, existing_config={"owner": "user"}
)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
settings["hooks"]["PreToolUse"].insert(
0, {"hooks": [], "label": "keep empty group"}
)
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
fixture.plugin.uninstall(AGENT_ID)
remaining = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert remaining["hooks"]["PreToolUse"] == [
{"hooks": [], "label": "keep empty group"}
]
assert "PostToolUse" not in remaining["hooks"]
def test_uninstall_removes_config_owned_entirely_by_elydora(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
fixture.plugin.uninstall(AGENT_ID)
assert fixture.config_path.exists() is False
def test_install_preserves_malformed_json_for_recovery(
def test_install_parses_hook_file_before_creating_runtime_files(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch, tmp_path, existing_config="{ malformed"
)
with pytest.raises(ValueError, match="parse Grok hooks config"):
fixture.plugin.install(fixture.config)
with pytest.raises(ValueError, match="parse Grok user hooks"):
fixture.install()
assert fixture.config_path.read_text(encoding="utf-8") == "{ malformed"
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert not fixture.agent_dir.exists()
@pytest.mark.parametrize(
"existing",
("existing", "pattern"),
[
{"hooks": None},
{"hooks": {"PreToolUse": None}},
{"hooks": {"PreToolUse": [None]}},
{"hooks": {"PreToolUse": [{"matcher": None, "hooks": []}]}},
{"hooks": {"PreToolUse": [{"matcher": 1, "hooks": []}]}},
{"hooks": {"PreToolUse": [{"hooks": None}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": "command", "command": ""}
]}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": "file", "command": "x"}
]}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": {"future": True}, "command": "x"}
]}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": "http", "url": ""}
]}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": "command", "command": "x", "timeout": 0}
]}]}},
{"hooks": {"PreToolUse": [{"hooks": [
{"type": "command", "command": "x", "timeout": float("inf")}
]}]}},
('{"hooks":{},"hooks":{}}', 'duplicate field "hooks"'),
({"hooks": None}, 'field "hooks" must be an object'),
({"hooks": {"PreToolUse": None}}, "must be an array"),
({"hooks": {"PreToolUse": [None]}}, "group.*must be an object"),
(
{"hooks": {"PreToolUse": [{"matcher": 1, "hooks": []}]}},
"matcher must be a string",
),
(
{"hooks": {"SessionStart": [{"matcher": "x", "hooks": []}]}},
"cannot declare a matcher",
),
(
{"hooks": {"PreToolUse": [{"hooks": [None]}]}},
"handler.*must be an object",
),
(
{"hooks": {"PreToolUse": [{"hooks": [{"type": "file"}]}]}},
"unsupported type",
),
(
{"hooks": {"PreToolUse": [{"hooks": [{
"type": "command", "command": ""
}]}]}},
"non-empty command",
),
(
{"hooks": {"PostToolUse": [{"hooks": [{
"type": "http", "url": ""
}]}]}},
"non-empty url",
),
(
{"hooks": {"PreToolUse": [{"hooks": [{
"type": "command", "command": "x", "timeout": -1
}]}]}},
"non-negative integer",
),
(
{"hooks": {"PreToolUse": [{"hooks": [{
"type": "command", "command": "x", "timeout": 1.5
}]}]}},
"non-negative integer",
),
(
'{"hooks":{"PreToolUse":[{"hooks":[{"type":"command",'
'"command":"x","timeout":NaN}]}]}}',
"invalid numeric constant",
),
(
{"hooks": {"PreToolUse": [{"hooks": [{
"type": "command", "command": "x", "env": {"A": 1}
}]}]}},
"env must map names to strings",
),
],
)
def test_install_rejects_invalid_native_shapes_before_writes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
existing: dict[str, Any],
existing: object,
pattern: str,
) -> None:
fixture = prepare_fixture(
monkeypatch, tmp_path, existing_config=existing
)
original = fixture.config_path.read_text(encoding="utf-8")
with pytest.raises(ValueError):
fixture.plugin.install(fixture.config)
with pytest.raises(ValueError, match=pattern):
fixture.install()
assert fixture.config_path.read_text(encoding="utf-8") == original
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert not fixture.agent_dir.exists()
def test_install_rejects_missing_guard_before_creating_files(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path, create_guard=False)
with pytest.raises(FileNotFoundError, match="guard runtime is missing"):
fixture.plugin.install(fixture.config)
assert fixture.config_path.exists() is False
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert fixture.private_key_path.exists() is False
def test_status_surfaces_malformed_referenced_runtime_metadata(
def test_install_migrates_exact_legacy_commands_and_preserves_lookalikes(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
fixture.runtime_config_path.write_text("{ malformed", encoding="utf-8")
lookalike = f"{legacy_command(fixture.guard_path)} --inspect"
legacy = {
"hooks": {
"PreToolUse": [
{"hooks": [{
"type": "command",
"command": legacy_command(fixture.guard_path),
"timeout": 10,
}]},
{"hooks": [{
"type": "command",
"command": lookalike,
"timeout": 10,
}]},
],
"PostToolUse": [{"hooks": [{
"type": "command",
"command": legacy_command(fixture.hook_path),
"timeout": 10,
}]}],
}
}
fixture.config_path.parent.mkdir(parents=True)
fixture.config_path.write_text(json.dumps(legacy), encoding="utf-8")
with pytest.raises(ValueError, match="parse Elydora runtime config"):
fixture.install()
settings = fixture.settings()
assert_managed_triple(settings)
assert any(
handler.get("command") == lookalike
for group in settings["hooks"]["PreToolUse"]
for handler in group["hooks"]
)
managed = settings["hooks"]["PreToolUse"][-1]
managed["hooks"].append({
"type": "command", "command": "user-command", "timeout": 10
})
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
fixture.plugin.uninstall(AGENT_ID)
remaining = fixture.settings()
assert remaining["hooks"]["PreToolUse"] == [
{"hooks": [{"type": "command", "command": lookalike, "timeout": 10}]},
{"hooks": [{"type": "command", "command": "user-command", "timeout": 10}]},
]
assert "PostToolUse" not in remaining["hooks"]
assert "PostToolUseFailure" not in remaining["hooks"]
def test_uninstall_preserves_user_config_and_removes_fully_managed_file(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
user = prepare_fixture(
monkeypatch,
tmp_path / "user",
existing_config={"owner": "user", "hooks": {"Notification": []}},
)
user.install()
user.plugin.uninstall(AGENT_ID)
assert user.settings() == {
"owner": "user",
"hooks": {"Notification": []},
}
managed = prepare_fixture(monkeypatch, tmp_path / "managed")
managed.install()
managed.plugin.uninstall(AGENT_ID)
assert not managed.config_path.exists()
def test_status_requires_one_complete_triple_identity_and_private_key(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
assert fixture.plugin.status()["installed"] is True
settings = fixture.settings()
del settings["hooks"]["PostToolUseFailure"]
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
assert fixture.plugin.status()["installed"] is False
fixture.install()
settings = fixture.settings()
settings["hooks"]["PostToolUseFailure"].append(
settings["hooks"]["PostToolUseFailure"][-1]
)
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
assert fixture.plugin.status()["installed"] is False
fixture.install()
fixture.private_key_path.write_text("invalid", encoding="utf-8")
with pytest.raises(ValueError, match="private key.*canonical 32-byte"):
fixture.plugin.status()
def test_atomic_writes_leave_no_temporary_files(
@pytest.mark.parametrize(
"runtime_name", ["guard.py", "hook.py", "config.json", "private.key"]
)
def test_status_requires_every_runtime_file(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, runtime_name: str
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
(fixture.agent_dir / runtime_name).unlink()
assert fixture.plugin.status()["installed"] is False
def test_installation_leaves_no_transaction_files(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
fixture.install()
for directory in (fixture.agent_dir, fixture.config_path.parent):
assert all(path.suffix != ".tmp" for path in directory.iterdir())
assert_no_transaction_files(fixture.home_dir)
if os.name != "nt":
for path in (
fixture.config_path,
fixture.runtime_config_path,
fixture.private_key_path,
):
assert path.stat().st_mode & 0o777 == 0o600
+158
View File
@@ -0,0 +1,158 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from grok_support import (
GrokApiHandler,
managed_handler,
prepare_fixture,
run_hook,
server_base_url,
start_api_server,
)
PRE_PAYLOAD = {
"hookEventName": "pre_tool_use",
"sessionId": "session-1",
"cwd": "C:/project",
"workspaceRoot": "C:/project",
"toolName": "run_terminal_command",
"toolInput": {"command": "npm test"},
"toolUseId": "tool-use-1",
"toolInputTruncated": False,
"timestamp": "2026-07-19T12:00:00Z",
}
def test_runtimes_enforce_active_state_and_preserve_native_payloads(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
server = start_api_server()
fixture = prepare_fixture(
monkeypatch, tmp_path, base_url=server_base_url(server)
)
try:
fixture.install()
settings = fixture.settings()
guard = managed_handler(settings, "PreToolUse")
success_audit = managed_handler(settings, "PostToolUse")
failure_audit = managed_handler(settings, "PostToolUseFailure")
guard_result = run_hook(
str(guard["command"]),
json.dumps(PRE_PAYLOAD),
fixture,
{"GROK_HOOK_EVENT": "injected-command-fragment"},
)
assert guard_result.returncode == 0, guard_result.stderr
assert guard_result.stdout == ""
success_payload = {
**PRE_PAYLOAD,
"hookEventName": "post_tool_use",
"toolResult": {"output": "tests passed"},
"toolResultTruncated": False,
"durationMs": 125,
}
success = run_hook(
str(success_audit["command"]),
json.dumps(success_payload),
fixture,
)
assert success.returncode == 0, success.stderr
failure_payload = {
**PRE_PAYLOAD,
"hookEventName": "post_tool_use_failure",
"toolResult": {"error": "command failed", "exitCode": 1},
"toolResultTruncated": False,
"durationMs": 40,
}
failure = run_hook(
str(failure_audit["command"]),
json.dumps(failure_payload),
fixture,
)
assert failure.returncode == 0, failure.stderr
operations = [
request["json"]
for request in GrokApiHandler.requests
if request["method"] == "POST"
]
assert len(operations) == 2
assert operations[0]["payload"] == success_payload
assert operations[1]["payload"] == failure_payload
assert operations[0]["subject"]["session_id"] == "session-1"
assert operations[1]["action"]["tool"] == "run_terminal_command"
finally:
server.shutdown()
server.server_close()
@pytest.mark.parametrize("status", ["frozen", "revoked"])
def test_guard_emits_official_deny_json_and_exit_code_two(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
status: str,
) -> None:
server = start_api_server(status=status)
fixture = prepare_fixture(
monkeypatch, tmp_path, base_url=server_base_url(server)
)
try:
fixture.install()
command = managed_handler(
fixture.settings(), "PreToolUse"
)["command"]
result = run_hook(str(command), json.dumps(PRE_PAYLOAD), fixture)
assert result.returncode == 2
assert status in result.stderr.lower()
decision = json.loads(result.stdout)
assert set(decision) == {"decision", "reason"}
assert decision["decision"] == "deny"
assert status in decision["reason"].lower()
finally:
server.shutdown()
server.server_close()
def test_audit_runtime_records_malformed_input_and_api_failures(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
server = start_api_server(operation_status=503)
fixture = prepare_fixture(
monkeypatch, tmp_path, base_url=server_base_url(server)
)
try:
fixture.install()
command = managed_handler(
fixture.settings(), "PostToolUse"
)["command"]
malformed = run_hook(str(command), "{ malformed", fixture)
assert malformed.returncode == 0
assert malformed.stderr == ""
upstream = run_hook(
str(command),
json.dumps({
**PRE_PAYLOAD,
"hookEventName": "post_tool_use",
"toolResult": {"output": "test"},
}),
fixture,
)
assert upstream.returncode == 0
assert upstream.stderr == ""
log = (fixture.agent_dir / "error.log").read_text(encoding="utf-8")
assert "invalid JSON" in log
assert "HTTP 503" in log
finally:
server.shutdown()
server.server_close()
+221
View File
@@ -0,0 +1,221 @@
from __future__ import annotations
import os
from pathlib import Path
import subprocess # nosec B404
import sys
import pytest
from grok_support import (
AGENT_ID,
VALID_PRIVATE_KEY,
prepare_fixture,
write_text,
)
def symlink_or_skip(
target: Path, link: Path, *, directory: bool = False
) -> None:
try:
link.symlink_to(target, target_is_directory=directory)
except OSError as error:
pytest.skip(f"Symbolic links unavailable: {error}")
def test_install_rejects_linked_hook_file_before_runtime_creation(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
target = tmp_path / "hooks-target.json"
source = '{"owner":"protected"}\n'
write_text(target, source)
fixture.config_path.parent.mkdir(parents=True)
symlink_or_skip(target, fixture.config_path)
with pytest.raises(OSError, match="not a physical file"):
fixture.install()
assert target.read_text(encoding="utf-8") == source
assert not fixture.agent_dir.exists()
@pytest.mark.parametrize("kind", ["home", "hooks", "runtime"])
def test_install_rejects_linked_directories_before_writes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
kind: str,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
target = tmp_path / f"{kind}-target"
target.mkdir()
if kind == "home":
link = fixture.grok_home
(target / "hooks").mkdir()
elif kind == "hooks":
link = fixture.config_path.parent
fixture.grok_home.mkdir(parents=True)
else:
link = fixture.agent_dir.parent
link.parent.mkdir(parents=True, exist_ok=True)
symlink_or_skip(target, link, directory=True)
with pytest.raises(OSError, match="physical directory"):
fixture.install()
untouched = fixture.config_path if kind == "runtime" else fixture.agent_dir
assert not untouched.exists()
def test_status_rejects_linked_private_key(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
target = tmp_path / "private-key-target"
source = fixture.private_key_path.read_text(encoding="utf-8")
write_text(target, source)
fixture.private_key_path.unlink()
symlink_or_skip(target, fixture.private_key_path)
with pytest.raises(OSError, match="not a physical file"):
fixture.plugin.status()
assert target.read_text(encoding="utf-8") == source
def test_preflight_inspects_every_runtime_artifact(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.agent_dir.mkdir(parents=True)
write_text(fixture.guard_path, "orphaned guard\n", 0o700)
target = tmp_path / "linked-audit-target"
write_text(target, "linked audit\n", 0o700)
symlink_or_skip(target, fixture.hook_path)
with pytest.raises(OSError, match="not a physical file"):
fixture.install()
assert target.read_text(encoding="utf-8") == "linked audit\n"
assert not fixture.config_path.exists()
@pytest.mark.parametrize("kind", ["orphaned", "mismatched"])
def test_install_rejects_unverifiable_runtime_identity(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
kind: str,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.agent_dir.mkdir(parents=True)
if kind == "orphaned":
write_text(fixture.guard_path, "orphaned guard\n", 0o700)
else:
write_text(
fixture.runtime_config_path,
'{"agent_name":"grok","agent_id":"another-agent"}\n',
)
pattern = (
"identity cannot be verified"
if kind == "orphaned"
else "identity does not match"
)
with pytest.raises(ValueError, match=pattern):
fixture.install()
assert not fixture.config_path.exists()
@pytest.mark.parametrize(
("field", "value", "pattern"),
[
("agent_name", "codex", "requires agent_name grok"),
("agent_id", "../escape", "Invalid agent ID"),
("private_key", "invalid", "canonical 32-byte"),
("token", "", "non-empty string"),
(
"base_url",
"https://api.elydora.com/path?token=secret",
"query parameters",
),
("guard_script_path", "outside", "managed agent directory"),
],
)
def test_install_validates_runtime_inputs_before_creating_files(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
field: str,
value: str,
pattern: str,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.config[field] = (
str(fixture.home_dir / "outside" / "guard.py")
if field == "guard_script_path"
else value
)
with pytest.raises(ValueError, match=pattern):
fixture.install()
assert not fixture.config_path.exists()
assert not fixture.agent_dir.exists()
def test_status_surfaces_malformed_runtime_metadata(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
fixture.runtime_config_path.write_text("{ malformed", encoding="utf-8")
with pytest.raises(ValueError, match="parse Elydora runtime config"):
fixture.plugin.status()
def test_cli_preflight_preserves_malformed_config_before_runtime_creation(
tmp_path: Path,
) -> None:
home_dir = tmp_path / "cli-home"
config_path = home_dir / ".grok" / "hooks" / "elydora-audit.json"
key_path = tmp_path / "private.key"
write_text(config_path, "{ malformed")
write_text(key_path, VALID_PRIVATE_KEY)
result = subprocess.run(
[
sys.executable,
"-m",
"elydora.cli",
"install",
"--agent",
"grok",
"--org_id",
"org-1",
"--agent_id",
AGENT_ID,
"--private_key_file",
str(key_path),
"--kid",
"kid-1",
"--base_url",
"https://api.elydora.com",
],
capture_output=True,
check=False,
cwd=Path(__file__).parents[1],
env={
**os.environ,
"HOME": str(home_dir),
"USERPROFILE": str(home_dir),
"GROK_HOME": "",
},
text=True,
)
assert result.returncode == 1
assert "parse Grok user hooks" in result.stderr
assert config_path.read_text(encoding="utf-8") == "{ malformed"
assert not (home_dir / ".elydora").exists()
+248
View File
@@ -0,0 +1,248 @@
from __future__ import annotations
import os
from pathlib import Path
import pytest
from elydora.plugins import _transaction, grok_installation
from elydora.plugins.grok_contract import (
build_grok_group,
remove_managed_grok_hooks,
render_grok_document,
)
from elydora.plugins.grok_io import read_grok_document
from elydora.utils import base64url_encode
from grok_support import (
AGENT_ID,
GrokFixture,
assert_no_transaction_files,
prepare_fixture,
write_text,
)
RUNTIME_NAMES = ("guard.py", "config.json", "private.key", "hook.py")
def prepare_installation(fixture: GrokFixture):
document = read_grok_document()
config = fixture.config
paths = grok_installation.preflight_grok_installation(config, document)
cleaned = remove_managed_grok_hooks(document.hooks)
hooks = {
**cleaned,
"PreToolUse": [
*cleaned.get("PreToolUse", []),
build_grok_group(paths.guard_path),
],
"PostToolUse": [
*cleaned.get("PostToolUse", []),
build_grok_group(paths.audit_path),
],
"PostToolUseFailure": [
*cleaned.get("PostToolUseFailure", []),
build_grok_group(paths.audit_path),
],
}
rendered = render_grok_document(document, hooks)
return grok_installation.prepare_grok_installation(
config, paths, rendered
)
def snapshot_files(paths: list[Path]) -> dict[Path, str]:
return {path: path.read_text(encoding="utf-8") for path in paths}
def assert_snapshot(snapshot: dict[Path, str]) -> None:
for path, source in snapshot.items():
assert path.read_text(encoding="utf-8") == source
def preserved_rollback(directory: Path, basename: str) -> Path:
matches = list(directory.glob(f".{basename}.*.rollback"))
assert len(matches) == 1, [path.name for path in directory.iterdir()]
return matches[0]
def test_install_restores_config_and_four_runtimes_when_final_commit_fails(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
original = '{"owner":"user"}\n'
fixture = prepare_fixture(
monkeypatch, tmp_path, existing_config=original
)
real_replace = _transaction.os.replace
failed = False
def fail_config_once(source: str, destination: str) -> None:
nonlocal failed
if os.path.abspath(destination) == str(fixture.config_path) and not failed:
failed = True
raise OSError("injected Grok config failure")
real_replace(source, destination)
monkeypatch.setattr(_transaction.os, "replace", fail_config_once)
with pytest.raises(OSError, match="injected Grok config failure"):
fixture.install()
assert fixture.config_path.read_text(encoding="utf-8") == original
for name in RUNTIME_NAMES:
assert not (fixture.agent_dir / name).exists()
assert_no_transaction_files(fixture.home_dir)
def test_install_rejects_concurrent_config_change_and_restores_runtimes(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_config={"owner": "user"},
)
changes = prepare_installation(fixture)
concurrent = '{"owner":"concurrent"}\n'
fixture.config_path.write_text(concurrent, encoding="utf-8")
with pytest.raises(OSError, match="changed during installation"):
grok_installation.commit_grok_installation(changes)
assert fixture.config_path.read_text(encoding="utf-8") == concurrent
for name in RUNTIME_NAMES:
assert not (fixture.agent_dir / name).exists()
assert_no_transaction_files(fixture.home_dir)
def test_uninstall_preserves_config_when_commit_fails(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_config={"owner": "user"},
)
fixture.install()
before = fixture.config_path.read_text(encoding="utf-8")
document = read_grok_document()
rendered = render_grok_document(
document,
remove_managed_grok_hooks(document.hooks, AGENT_ID),
)
changes = grok_installation.prepare_grok_uninstall(rendered)
def fail_replace(_source: str, _destination: str) -> None:
raise OSError("injected Grok uninstall failure")
monkeypatch.setattr(_transaction.os, "replace", fail_replace)
with pytest.raises(OSError, match="injected Grok uninstall failure"):
grok_installation.commit_grok_uninstall(changes)
assert fixture.config_path.read_text(encoding="utf-8") == before
assert_no_transaction_files(fixture.home_dir)
def test_recovery_preserves_original_after_committed_file_changes(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
original = fixture.runtime_config_path.read_text(encoding="utf-8")
fixture.config["org_id"] = "org-updated"
changes = prepare_installation(fixture)
real_replace = _transaction.os.replace
calls = 0
def change_first_then_fail(source: str, destination: str) -> None:
nonlocal calls
calls += 1
if calls == 1:
real_replace(source, destination)
write_text(Path(destination), "external change\n")
return
raise OSError("injected later commit failure")
monkeypatch.setattr(
_transaction.os, "replace", change_first_then_fail
)
with pytest.raises(OSError, match="original content preserved at"):
grok_installation.commit_grok_installation(changes)
assert fixture.runtime_config_path.read_text(encoding="utf-8") == (
"external change\n"
)
rollback = preserved_rollback(fixture.agent_dir, "config.json")
assert rollback.read_text(encoding="utf-8") == original
def test_recovery_preserves_original_after_restore_failure(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.install()
original = fixture.runtime_config_path.read_text(encoding="utf-8")
fixture.config["org_id"] = "org-updated"
fixture.config["private_key"] = base64url_encode(
bytes(range(31, -1, -1))
)
changes = prepare_installation(fixture)
real_replace = _transaction.os.replace
commits = 0
def fail_commit_and_restore(source: str, destination: str) -> None:
nonlocal commits
if source.endswith(".rollback"):
raise OSError("injected rollback failure")
commits += 1
if commits == 2:
raise OSError("injected later commit failure")
real_replace(source, destination)
monkeypatch.setattr(
_transaction.os, "replace", fail_commit_and_restore
)
with pytest.raises(OSError, match="original content preserved at"):
grok_installation.commit_grok_installation(changes)
rollback = preserved_rollback(fixture.agent_dir, "config.json")
assert rollback.read_text(encoding="utf-8") == original
def test_prepared_install_detects_change_before_commit(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_config={"owner": "user"},
)
changes = prepare_installation(fixture)
concurrent = '{"owner":"concurrent-before-stage"}\n'
fixture.config_path.write_text(concurrent, encoding="utf-8")
with pytest.raises(OSError, match="changed during installation"):
grok_installation.commit_grok_installation(changes)
assert fixture.config_path.read_text(encoding="utf-8") == concurrent
assert_no_transaction_files(fixture.home_dir)
def test_transaction_rejects_oversized_source_before_staging(
tmp_path: Path,
) -> None:
target = tmp_path / "managed" / "config.json"
with pytest.raises(ValueError, match="exceeds 4 bytes"):
_transaction.file_change(
str(target),
"managed test source",
"five!",
0o600,
maximum_bytes=4,
)
assert not target.parent.exists()