feat(runtime): enforce governed mutation boundaries
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
parent
e3c6124e22
commit
20e6f381f6
28 changed files with 1068 additions and 154 deletions
|
|
@ -6,6 +6,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from rein_aharness.metrics import external_metrics_dir
|
||||
from rein_aharness.repository_grant import RepositoryGrant, RepositoryGrantError
|
||||
from rein_aharness.repository_transaction import RepositoryTransaction
|
||||
from rein_aharness.runner import run_task
|
||||
|
|
@ -242,18 +243,129 @@ def test_taskspec_file_wraps_invalid_repository_grant(tmp_path: Path) -> None:
|
|||
TaskSpec.from_file(task_file)
|
||||
|
||||
|
||||
def test_runner_refuses_grant_before_adapter_dispatch_or_mutation(tmp_path: Path) -> None:
|
||||
def test_runner_accepts_granted_commit_and_keeps_checkout_clean(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
|
||||
class Adapter:
|
||||
def execute_prompt(self, prompt: str, config: object):
|
||||
docs = repo / "docs"
|
||||
docs.mkdir()
|
||||
(docs / "result.md").write_text("accepted\n", encoding="utf-8")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"-c",
|
||||
"user.name=test",
|
||||
"commit",
|
||||
"-qm",
|
||||
"accepted result",
|
||||
],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
from llm_connect.models import LLMResponse
|
||||
|
||||
return LLMResponse(
|
||||
content="done",
|
||||
model="fake",
|
||||
usage={},
|
||||
finish_reason="stop",
|
||||
)
|
||||
|
||||
result = run_task(
|
||||
TaskSpec(
|
||||
title="bounded change",
|
||||
description="update docs",
|
||||
target_repo=repo,
|
||||
repository_grant=RepositoryGrant.from_mapping(_grant()),
|
||||
),
|
||||
adapter=Adapter(),
|
||||
report_to_hub=False,
|
||||
write_metrics=True,
|
||||
)
|
||||
|
||||
assert result.ok is True
|
||||
assert result.committed is True
|
||||
assert result.transaction is not None
|
||||
assert result.transaction["acceptance"]["accepted"] is True
|
||||
assert result.transaction["acceptance"]["changed_paths"] == ["docs/result.md"]
|
||||
assert result.transaction["repository_grant"]["grant_id"]
|
||||
assert result.transaction["metrics"] == {
|
||||
"storage": "external",
|
||||
"session_id": result.transaction["transaction_id"],
|
||||
"projection_ready": True,
|
||||
}
|
||||
assert _git(repo, "status", "--porcelain=v2") == ""
|
||||
assert not (repo / ".kaizen" / "metrics").exists()
|
||||
metric_path = external_metrics_dir(repo, "coach") / "executions.jsonl"
|
||||
metric_record = json.loads(metric_path.read_text(encoding="utf-8").strip())
|
||||
assert metric_record["session_id"] == result.transaction["transaction_id"]
|
||||
|
||||
|
||||
def test_runner_rejects_commit_outside_repository_grant(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
|
||||
class Adapter:
|
||||
def execute_prompt(self, prompt: str, config: object):
|
||||
(repo / "UNRELATED.md").write_text("not granted\n", encoding="utf-8")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"-c",
|
||||
"user.name=test",
|
||||
"commit",
|
||||
"-qm",
|
||||
"ungranted result",
|
||||
],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
from llm_connect.models import LLMResponse
|
||||
|
||||
return LLMResponse(
|
||||
content="done",
|
||||
model="fake",
|
||||
usage={},
|
||||
finish_reason="stop",
|
||||
)
|
||||
|
||||
result = run_task(
|
||||
TaskSpec(
|
||||
title="bounded change",
|
||||
description="update docs",
|
||||
target_repo=repo,
|
||||
repository_grant=RepositoryGrant.from_mapping(_grant()),
|
||||
),
|
||||
adapter=Adapter(),
|
||||
report_to_hub=False,
|
||||
write_metrics=True,
|
||||
)
|
||||
|
||||
assert result.ok is False
|
||||
assert result.committed is True
|
||||
assert result.reason.startswith("repository acceptance failed: path-not-granted")
|
||||
assert result.transaction is not None
|
||||
assert "acceptance" not in result.transaction
|
||||
|
||||
|
||||
def test_runner_refuses_grant_when_durable_metrics_are_disabled(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
called = False
|
||||
|
||||
class Adapter:
|
||||
def execute_prompt(self, prompt: str, config: object) -> None:
|
||||
def execute_prompt(self, prompt: str, config: object):
|
||||
nonlocal called
|
||||
called = True
|
||||
raise AssertionError("adapter must not be dispatched")
|
||||
|
||||
head_before = _git(repo, "rev-parse", "HEAD")
|
||||
status_before = _git(repo, "status", "--porcelain=v2")
|
||||
result = run_task(
|
||||
TaskSpec(
|
||||
title="bounded change",
|
||||
|
|
@ -267,11 +379,69 @@ def test_runner_refuses_grant_before_adapter_dispatch_or_mutation(tmp_path: Path
|
|||
)
|
||||
|
||||
assert result.ok is False
|
||||
assert result.committed is False
|
||||
assert result.reason.startswith("refused: repository_grant enforcement")
|
||||
assert "require durable external metrics" in result.reason
|
||||
assert called is False
|
||||
assert _git(repo, "rev-parse", "HEAD") == head_before
|
||||
assert _git(repo, "status", "--porcelain=v2") == status_before
|
||||
|
||||
|
||||
def test_runner_fails_granted_result_when_external_metrics_cannot_persist(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
|
||||
class Adapter:
|
||||
def execute_prompt(self, prompt: str, config: object):
|
||||
docs = repo / "docs"
|
||||
docs.mkdir()
|
||||
(docs / "result.md").write_text("accepted\n", encoding="utf-8")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"-c",
|
||||
"user.name=test",
|
||||
"commit",
|
||||
"-qm",
|
||||
"accepted result",
|
||||
],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
from llm_connect.models import LLMResponse
|
||||
|
||||
return LLMResponse(
|
||||
content="done",
|
||||
model="fake",
|
||||
usage={},
|
||||
finish_reason="stop",
|
||||
)
|
||||
|
||||
def fail_metrics(*args: object, **kwargs: object) -> None:
|
||||
raise OSError("state volume unavailable")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"rein_aharness.runner.metrics.record_external_execution",
|
||||
fail_metrics,
|
||||
)
|
||||
result = run_task(
|
||||
TaskSpec(
|
||||
title="bounded change",
|
||||
description="update docs",
|
||||
target_repo=repo,
|
||||
repository_grant=RepositoryGrant.from_mapping(_grant()),
|
||||
),
|
||||
adapter=Adapter(),
|
||||
report_to_hub=False,
|
||||
write_metrics=True,
|
||||
)
|
||||
|
||||
assert result.ok is False
|
||||
assert result.reason == "required external metrics persistence failed (OSError)"
|
||||
assert result.transaction is not None
|
||||
assert result.transaction["acceptance"]["accepted"] is True
|
||||
assert _git(repo, "status", "--porcelain=v2") == ""
|
||||
|
||||
|
||||
def _git(repo: Path, *args: str) -> str:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue