feat: expose cleanup and reporting outcomes for gateway runs
Some checks failed
ci / validate (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
tegwick 2026-09-06 11:12:30 +02:00
parent f76cb7d955
commit 356e34992a
6 changed files with 177 additions and 7 deletions

View file

@ -201,6 +201,8 @@ def test_owner_session_failure_still_destroys_sandbox(failure):
rein=ReinAharness(), manager=manager)
assert not result.ok
assert result.evidence.failure_stage == "session_start"
assert result.evidence.session_cleanup == "not_attempted"
assert result.evidence.sandbox_destroy == "succeeded"
manager.destroy.assert_called_once_with("sbx1")
host_run.assert_not_called()
@ -415,3 +417,105 @@ def test_manager_initialization_failure_returns_redacted_evidence():
assert result.tool_error == "private manager configuration"
assert "private manager configuration" not in str(post.call_args)
post.assert_called_once()
@pytest.mark.parametrize('cleanup_fails,destroy_fails', [(True, False), (False, True), (True, True)])
def test_teardown_outcomes_survive_original_execution_failure(cleanup_fails, destroy_fails):
manager = MagicMock()
manager.create.return_value = _fake_status()
if destroy_fails:
manager.destroy.side_effect = RuntimeError('private destroy failure')
class FailingRein(_FakeRein):
def dispatch_tool(self, session, tool_call):
raise RuntimeError('original execution failure')
def cleanup_session(self, session):
if cleanup_fails:
raise RuntimeError('private cleanup failure')
with patch('glas_harness.gateway.hub.post_progress_event', return_value=True) as post:
result = run_execution(_request(report_to_hub=True), catalog=_catalog_with_readiness(),
rein=FailingRein(), manager=manager)
assert not result.ok
assert result.evidence.failure_stage == 'execution'
assert result.tool_error == 'original execution failure'
assert result.evidence.session_cleanup == ('failed' if cleanup_fails else 'succeeded')
assert result.evidence.sandbox_destroy == ('failed' if destroy_fails else 'succeeded')
detail = post.call_args.kwargs['detail']
assert detail['session_cleanup'] == result.evidence.session_cleanup
assert detail['sandbox_destroy'] == result.evidence.sandbox_destroy
assert 'private' not in str(detail)
manager.destroy.assert_called_once_with('sbx1')
@pytest.mark.parametrize('report,accepted,expected', [
(False, True, 'not_requested'), (True, True, 'accepted'), (True, False, 'failed')
])
def test_reporting_outcome_does_not_change_execution(report, accepted, expected):
manager = MagicMock()
manager.create.return_value = _fake_status()
rein = _FakeRein()
with patch('glas_harness.gateway.hub.post_progress_event', return_value=accepted) as post:
result = run_execution(_request(report_to_hub=report), catalog=_catalog_with_readiness(),
rein=rein, manager=manager)
assert result.ok
assert result.hub_report_status == expected
assert result.evidence.session_cleanup == 'succeeded'
assert result.evidence.sandbox_destroy == 'succeeded'
assert post.call_count == int(report)
assert rein.calls.count('dispatch_tool') == 1
def test_refusal_reports_no_teardown_attempt_and_reporting_failure():
with patch('glas_harness.gateway.hub.post_progress_event', return_value=False):
result = run_execution(_request(report_to_hub=True))
assert result.evidence.outcome == 'refused'
assert result.evidence.session_cleanup == 'not_attempted'
assert result.evidence.sandbox_destroy == 'not_attempted'
assert result.hub_report_status == 'failed'
@pytest.mark.parametrize('http_failure', ['timeout', 'status'])
def test_http_reporting_failure_preserves_success(http_failure):
import httpx
manager = MagicMock()
manager.create.return_value = _fake_status()
with patch('glas_harness.hub.httpx.post') as post:
if http_failure == 'timeout':
post.side_effect = httpx.ConnectTimeout('private transport detail')
else:
post.return_value = httpx.Response(503, request=httpx.Request('POST', 'http://hub/progress/'))
result = run_execution(_request(report_to_hub=True), catalog=_catalog_with_readiness(),
rein=_FakeRein(), manager=manager)
assert result.ok
assert result.hub_report_status == 'failed'
assert 'private transport detail' not in result.model_dump_json()
post.assert_called_once()
manager.destroy.assert_called_once_with('sbx1')
def test_cleanup_failure_after_success_still_destroys_sandbox():
manager = MagicMock()
manager.create.return_value = _fake_status()
rein = _FakeRein()
rein.cleanup_session = MagicMock(side_effect=RuntimeError('cleanup failed'))
result = run_execution(_request(), catalog=_catalog_with_readiness(),
rein=rein, manager=manager)
assert not result.ok
assert result.evidence.failure_stage == 'teardown'
assert result.evidence.session_cleanup == 'failed'
assert result.evidence.sandbox_destroy == 'succeeded'
manager.destroy.assert_called_once_with('sbx1')
def test_old_result_records_have_unknown_outcomes():
from glas_harness.contract import GatewayResult
old_record = run_execution(_request()).model_dump()
old_record.pop('hub_report_status')
old_record['evidence'].pop('session_cleanup')
old_record['evidence'].pop('sandbox_destroy')
restored = GatewayResult.model_validate(old_record)
assert restored.hub_report_status == 'unknown'
assert restored.evidence.session_cleanup == 'unknown'
assert restored.evidence.sandbox_destroy == 'unknown'