Refuse close after lease loss

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
This commit is contained in:
tegwick 2026-08-23 14:37:18 +02:00
parent 9097d69e1d
commit 75f4914777
3 changed files with 68 additions and 1 deletions

View file

@ -179,6 +179,23 @@ def process_one(
finally:
hb.stop()
if hb.monitor.lost:
loss = hb.monitor.evidence
assert loss is not None
return ProcessResult(
claimed=True,
run_id=run.id,
approach=approach,
ok=False,
reason=f"lease lost ({loss.error_type})",
detail={
"lease_loss": {
"error_type": loss.error_type,
"observed_at": loss.observed_at,
}
},
)
payload = {
"approach": ar.approach,
"ok": ar.ok,
@ -277,6 +294,23 @@ def _process_profiled_run(
finally:
hb.stop()
if hb.monitor.lost:
loss = hb.monitor.evidence
assert loss is not None
return ProcessResult(
claimed=True,
run_id=run.id,
approach=GLAS_APPROACH,
ok=False,
reason=f"lease lost ({loss.error_type})",
detail={
"lease_loss": {
"error_type": loss.error_type,
"observed_at": loss.observed_at,
}
},
)
evidence = gateway_result["evidence"]
ok = gateway_result["ok"]
reason = str(evidence.get("error") or evidence.get("outcome") or "Glas execution failed")

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from unittest.mock import MagicMock, patch
import time
from rein_aharness.approaches import ApproachResult, APPROACH_FI_RESEARCH_BRIEF
from rein_aharness.claim_loop import process_one, poll_peek
@ -105,6 +106,35 @@ def test_process_one_failure_reopens() -> None:
assert client.fail.call_args.kwargs["reopen"] is True
def test_process_one_refuses_close_after_lease_loss() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.config = OpsRunConfig(worker_id="w", lease_seconds=90)
client.claim.return_value = [_claimed_run()]
client.heartbeat.side_effect = OpsRunError("lease rejected")
ar = ApproachResult(
ok=True,
approach=APPROACH_FI_RESEARCH_BRIEF,
result={"path": "briefs/x.md"},
reason="ok",
)
def slow_execute(*_args, **_kwargs):
time.sleep(0.05)
return ar
with (
patch("rein_aharness.claim_loop._heartbeat_interval", return_value=0.01),
patch("rein_aharness.claim_loop.execute_approach", side_effect=slow_execute),
):
result = process_one(client)
assert result.ok is False
assert result.reason.startswith("lease lost")
assert "lease_loss" in result.detail
client.complete.assert_not_called()
client.fail.assert_not_called()
def test_poll_peek() -> None:
client = MagicMock(spec=ActivityCoreOpsClient)
client.list_open.return_value = [_claimed_run()]

View file

@ -201,7 +201,10 @@ evidence instead of raw provider exception text. This establishes the signal
needed by execution and acceptance boundaries without pretending that adapters
can already cancel in-flight work. Remaining work is to classify Activity Core
responses, connect cancellation at each adapter boundary, and refuse result
acceptance/close after loss.
acceptance/close after loss. The claim loop now implements that last refusal for
normal and successful profiled executions: a lost lease returns bounded loss
evidence and skips Activity Core completion/failure calls, leaving reconciliation
to the owner of the expired lease.
## Verify accepted commits and reconcile metrics/reporting