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:
tegwick 2026-09-04 11:25:07 +02:00
parent e3c6124e22
commit 20e6f381f6
28 changed files with 1068 additions and 154 deletions

12
tests/conftest.py Normal file
View file

@ -0,0 +1,12 @@
from __future__ import annotations
from pathlib import Path
import pytest
@pytest.fixture(autouse=True)
def isolated_runtime_state(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Keep repository locks and close evidence out of operator-owned state."""
monkeypatch.setenv("REIN_AHARNESS_STATE_DIR", str(tmp_path / "runtime-state"))
monkeypatch.setenv("AGENT_HARNESS_LEGACY_APPROACHES_UNTIL", "2099-12-31")

View file

@ -2,6 +2,8 @@
from __future__ import annotations
from datetime import date
from rein_aharness.approaches import (
APPROACH_AGENT_SESSION,
APPROACH_BRIEF_DAILY,
@ -9,6 +11,8 @@ from rein_aharness.approaches import (
APPROACH_FI_RESEARCH_BRIEF,
APPROACH_MAIL_PIPELINE,
APPROACH_UNMATCHED,
execute_approach,
legacy_approaches_enabled,
select_approach,
)
from rein_aharness.ops_run_client import OpsRun
@ -113,3 +117,26 @@ def test_fi_before_generic_automated() -> None:
assert (
select_approach(_run(labels=["research-brief"])) == APPROACH_FI_RESEARCH_BRIEF
)
def test_legacy_approaches_require_non_expired_explicit_date() -> None:
today = date(2026, 9, 4)
assert legacy_approaches_enabled("2026-09-04", today=today) is True
assert legacy_approaches_enabled("2026-09-03", today=today) is False
assert legacy_approaches_enabled("true", today=today) is False
assert legacy_approaches_enabled("", today=today) is False
def test_execute_approach_refuses_profile_absent_route_without_flag(
monkeypatch,
) -> None:
monkeypatch.delenv("AGENT_HARNESS_LEGACY_APPROACHES_UNTIL")
result = execute_approach(
_run(labels=["research-brief"], target_repo="not-resolved"),
report_to_hub=False,
)
assert result.ok is False
assert result.reopen is False
assert "compatibility routing is disabled" in result.reason

View file

@ -2,8 +2,10 @@
from __future__ import annotations
from unittest.mock import MagicMock, patch
import subprocess
import time
from pathlib import Path
from unittest.mock import MagicMock, patch
from rein_aharness.approaches import ApproachResult, APPROACH_FI_RESEARCH_BRIEF
from rein_aharness.claim_loop import (
@ -20,6 +22,7 @@ from rein_aharness.ops_run_client import (
OpsRunConfig,
OpsRunError,
)
from rein_aharness.repository_transaction import RepositoryTransaction
def _claimed_run() -> OpsRun:
@ -37,6 +40,41 @@ def _claimed_run() -> OpsRun:
)
def _profiled_case(
tmp_path: Path,
) -> tuple[Path, OpsRun, MagicMock]:
repo = tmp_path / "freedom-intelligence"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
(repo / "README.md").write_text("controlled target\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",
"baseline",
],
cwd=repo,
check=True,
)
run = _claimed_run()
run.harness_profile_ref = "harness.agent-dev-local@1.0.0"
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(
worker_id="w",
lease_seconds=90,
repo_roots=(str(tmp_path),),
)
client.claim.return_value = [run]
return repo, run, client
def test_process_one_empty() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
@ -231,12 +269,8 @@ def test_process_one_records_adapter_exception_without_unbound_result() -> None:
client.fail.assert_not_called()
def test_profiled_exception_after_lease_loss_skips_close() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
run = _claimed_run()
run.harness_profile_ref = "harness.agent-dev-local@1.0.0"
client.claim.return_value = [run]
def test_profiled_exception_after_lease_loss_skips_close(tmp_path: Path) -> None:
repo, _run, client = _profiled_case(tmp_path)
client.heartbeat.side_effect = OpsRunError("lease rejected", status_code=409)
def slow_profile(*_args, **_kwargs):
@ -253,6 +287,8 @@ def test_profiled_exception_after_lease_loss_skips_close() -> None:
assert result.reason.startswith("lease lost")
client.complete.assert_not_called()
client.fail.assert_not_called()
with RepositoryTransaction(repo) as retry:
assert retry.locked is True
def test_poll_peek() -> None:
@ -263,13 +299,11 @@ def test_poll_peek() -> None:
assert rows[0]["approach"] == APPROACH_FI_RESEARCH_BRIEF
def test_profiled_run_uses_glas_and_completes_with_full_result() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
run = _claimed_run()
run.harness_profile_ref = "harness.agent-dev-local@1.0.0"
def test_profiled_run_uses_glas_and_completes_with_full_result(
tmp_path: Path,
) -> None:
_repo, run, client = _profiled_case(tmp_path)
run.approach_hint = "fi-research-brief"
client.claim.return_value = [run]
client.complete.return_value = OpsRun(
id=run.id,
activity_definition_id="def",
@ -313,13 +347,12 @@ def test_profiled_run_uses_glas_and_completes_with_full_result() -> None:
execute.assert_not_called()
def test_profile_refusal_fails_terminally_without_legacy_fallback() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
run = _claimed_run()
def test_profile_refusal_fails_terminally_without_legacy_fallback(
tmp_path: Path,
) -> None:
_repo, run, client = _profiled_case(tmp_path)
run.harness_profile_ref = "harness.unknown@9.9.9"
run.approach_hint = "fi-research-brief"
client.claim.return_value = [run]
client.fail.return_value = OpsRun(
id=run.id,
activity_definition_id="def",
@ -346,6 +379,49 @@ def test_profile_refusal_fails_terminally_without_legacy_fallback() -> None:
execute.assert_not_called()
def test_profiled_signal_cancellation_releases_repository_lock(
tmp_path: Path,
) -> None:
repo, _run, client = _profiled_case(tmp_path)
def cancel_during_gateway(*_args, **_kwargs):
_cancel_active_run("signal")
return {"ok": True, "evidence": {"outcome": "late-success"}}
with patch(
"rein_aharness.claim_loop.execute_profiled_run",
side_effect=cancel_during_gateway,
):
result = process_one(client)
assert result.ok is False
assert result.reason == "execution cancelled (signal)"
client.complete.assert_not_called()
client.fail.assert_not_called()
with RepositoryTransaction(repo) as retry:
assert retry.locked is True
def test_profiled_close_failure_happens_after_repository_lock_release(
tmp_path: Path,
) -> None:
repo, _run, client = _profiled_case(tmp_path)
client.complete.side_effect = OpsRunError("complete transport failed")
gateway_result = {"ok": True, "evidence": {"outcome": "succeeded"}}
with patch(
"rein_aharness.claim_loop.execute_profiled_run",
return_value=gateway_result,
):
result = process_one(client)
assert result.ok is False
assert result.reason.startswith("close ops_run failed:")
assert "repository_transaction" in result.detail
with RepositoryTransaction(repo) as retry:
assert retry.locked is True
def test_poll_peek_reports_authoritative_profile_route() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
run = _claimed_run()

View file

@ -0,0 +1,67 @@
from __future__ import annotations
import json
import subprocess
from datetime import date
from pathlib import Path
from rein_aharness import fi_research_brief
def _repo(tmp_path: Path) -> Path:
repo = tmp_path / "freedom-intelligence"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
(repo / "README.md").write_text("baseline\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",
"baseline",
],
cwd=repo,
check=True,
)
return repo
def test_fi_brief_never_pushes_even_when_legacy_env_requests_it(
tmp_path: Path,
monkeypatch,
) -> None:
repo = _repo(tmp_path)
monkeypatch.setenv("FI_RESEARCH_BRIEF_PUSH", "1")
original_git = fi_research_brief._git
calls: list[tuple[str, ...]] = []
def observed_git(target: Path, *args: str) -> str:
calls.append(args)
return original_git(target, *args)
monkeypatch.setattr(fi_research_brief, "_git", observed_git)
result = fi_research_brief.run_fi_research_brief(
repo,
day=date(2026, 9, 4),
report_to_hub=False,
complete_fn=lambda _prompt: json.dumps(
{
"headline_deltas": ["No material delta after allowlist review."],
"axis_a": [],
"axis_b": [],
"axis_c": [],
"axis_d": [],
"collection_candidates": [],
"lab_implications": ["Recheck tomorrow."],
}
),
)
assert result.ok is True
assert result.committed is True
assert not any(args and args[0] == "push" for args in calls)

View file

@ -3,7 +3,14 @@ from __future__ import annotations
import json
from pathlib import Path
from rein_aharness.metrics import record_execution, regenerate_summary
import pytest
from rein_aharness.metrics import (
external_metrics_dir,
record_execution,
record_external_execution,
regenerate_summary,
)
def test_record_execution_writes_jsonl_and_summary(tmp_path: Path) -> None:
@ -49,3 +56,89 @@ def test_summary_aggregates_multiple(tmp_path: Path) -> None:
def test_regenerate_summary_empty() -> None:
assert regenerate_summary("x", [])["execution_count"] == 0
def test_external_metrics_are_durable_and_projection_ready(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
state_dir = tmp_path / "state"
path = record_external_execution(
repo,
"coach",
success=True,
committed=True,
head_after="abc123",
metadata={"repository_grant_id": "grant-1"},
session_id="transaction-1",
state_dir=state_dir,
)
directory = external_metrics_dir(repo, "coach", state_dir=state_dir)
assert path == directory / "executions.jsonl"
assert path.stat().st_mode & 0o777 == 0o600
assert directory.stat().st_mode & 0o777 == 0o700
record = json.loads(path.read_text(encoding="utf-8").strip())
assert record["metadata"]["repository_grant_id"] == "grant-1"
assert record["session_id"] == "transaction-1"
projection = json.loads(
(directory / "projection.json").read_text(encoding="utf-8")
)
assert projection["repository_name"] == "repo"
assert projection["target_relative_directory"] == ".kaizen/metrics/coach"
assert not (repo / ".kaizen").exists()
def test_external_metrics_deduplicate_transaction_replay(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
state_dir = tmp_path / "state"
for _attempt in range(2):
path = record_external_execution(
repo,
"coach",
success=True,
session_id="transaction-1",
state_dir=state_dir,
)
assert len(path.read_text(encoding="utf-8").splitlines()) == 1
summary = json.loads((path.parent / "summary.json").read_text(encoding="utf-8"))
assert summary["execution_count"] == 1
def test_external_metrics_refuse_an_incomplete_ledger(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
state_dir = tmp_path / "state"
directory = external_metrics_dir(repo, "coach", state_dir=state_dir)
directory.mkdir(parents=True)
ledger = directory / "executions.jsonl"
ledger.write_text('{"incomplete":true}', encoding="utf-8")
with pytest.raises(OSError, match="incomplete record"):
record_external_execution(
repo,
"coach",
success=True,
state_dir=state_dir,
)
assert ledger.read_text(encoding="utf-8") == '{"incomplete":true}'
def test_external_metrics_refuse_unsafe_projection_agent(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
state_dir = tmp_path / "state"
with pytest.raises(OSError, match="safe metrics projection"):
record_external_execution(
repo,
"../escape",
success=True,
state_dir=state_dir,
)
assert not state_dir.exists()

View file

@ -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:

View file

@ -78,9 +78,15 @@ def test_transaction_captures_clean_branch_and_remote_refs(tmp_path: Path) -> No
def test_dirty_baseline_is_refused_without_changing_user_files(tmp_path: Path) -> None:
repo = _make_repo(tmp_path)
subprocess.run(
["git", "update-ref", "refs/remotes/origin/main", "HEAD"],
cwd=repo,
check=True,
)
changed = repo / "README.md"
changed.write_text("operator change\n", encoding="utf-8")
before = _status(repo)
remote_refs_before = _remote_refs(repo)
with pytest.raises(DirtyRepositoryError) as excinfo:
with RepositoryTransaction(repo, state_dir=tmp_path / "state"):
@ -90,6 +96,7 @@ def test_dirty_baseline_is_refused_without_changing_user_files(tmp_path: Path) -
assert excinfo.value.baseline.dirty_entries == 1
assert changed.read_text(encoding="utf-8") == "operator change\n"
assert _status(repo) == before
assert _remote_refs(repo) == remote_refs_before
# Refusal released the lock; inspection can explicitly opt into dirty state.
with RepositoryTransaction(
@ -469,3 +476,18 @@ def _status(repo: Path) -> str:
capture_output=True,
text=True,
).stdout
def _remote_refs(repo: Path) -> str:
return subprocess.run(
[
"git",
"for-each-ref",
"--format=%(refname) %(objectname)",
"refs/remotes",
],
cwd=repo,
check=True,
capture_output=True,
text=True,
).stdout

View file

@ -8,6 +8,7 @@ import pytest
import yaml
from rein_aharness.manifest import HARNESS_MAJOR
from rein_aharness.repository_transaction import RepositoryTransaction
from rein_aharness.runner import RunResult, run_task
from rein_aharness.taskspec import TaskSpec, TaskSpecError
@ -148,6 +149,26 @@ def test_run_task_records_bounded_cancellation_without_session_output(tmp_path)
assert result.reason == "execution cancelled (lease-loss)"
def test_run_task_timeout_releases_repository_lock(tmp_path) -> None:
repo = _make_repo(tmp_path)
class TimingOutAdapter:
def execute_prompt(self, prompt, config):
raise TimeoutError("adapter deadline elapsed")
result = run_task(
_spec(repo),
adapter=TimingOutAdapter(),
report_to_hub=False,
write_metrics=False,
)
assert result.ok is False
assert result.reason == "session failed: adapter deadline elapsed"
with RepositoryTransaction(repo) as retry:
assert retry.locked is True
def test_run_task_refuses_unknown_tool_profile(tmp_path) -> None:
repo = _make_repo(tmp_path)
_write_manifest(