|
22 | 22 | CaseRange, |
23 | 23 | CaseResult, |
24 | 24 | CaseStatus, |
| 25 | + DiagnosticSeverity, |
25 | 26 | DisclosureLevel, |
26 | 27 | EvaluationAccessPolicy, |
27 | 28 | EvaluationAuthorization, |
|
31 | 32 | EvaluationDatabase, |
32 | 33 | EvaluationDefinition, |
33 | 34 | EvaluationDeniedError, |
| 35 | + EvaluationDiagnostic, |
34 | 36 | EvaluationEngine, |
35 | 37 | EvaluationExecutionError, |
| 38 | + EvaluationInfrastructureError, |
36 | 39 | EvaluationLimits, |
37 | 40 | EvaluationPlan, |
38 | 41 | EvaluationPrincipal, |
@@ -848,6 +851,64 @@ async def failing_refund(*args, **kwargs): |
848 | 851 | assert isinstance(raised.value.__cause__, OSError) |
849 | 852 |
|
850 | 853 |
|
| 854 | +@pytest.mark.asyncio |
| 855 | +async def test_engine_refund_failure_preserves_the_infrastructure_error( |
| 856 | + tmp_path: Path, monkeypatch |
| 857 | +): |
| 858 | + """The infrastructure-failure refund must not substitute its own OSError. |
| 859 | +
|
| 860 | + This path builds its exception after the refund rather than inside an |
| 861 | + `except`, so a failing refund used to preempt the `raise` entirely. The |
| 862 | + sidecar maps EvaluationInfrastructureError to "infrastructure failure" for |
| 863 | + the agent; a bare OSError reaches the unmapped branch and is reported as |
| 864 | + "evaluation failed: OSError" instead. |
| 865 | + """ |
| 866 | + workspace = StubWorkspace(tmp_path / "repo") |
| 867 | + backend = StubBackend( |
| 868 | + report=EvaluationReport( |
| 869 | + status=EvaluationStatus.INVALID, |
| 870 | + diagnostics=[ |
| 871 | + EvaluationDiagnostic( |
| 872 | + code="infrastructure_failure", |
| 873 | + message="the sandbox host went away", |
| 874 | + severity=DiagnosticSeverity.ERROR, |
| 875 | + ) |
| 876 | + ], |
| 877 | + ), |
| 878 | + cost=EvaluationCost(cases=1), |
| 879 | + ) |
| 880 | + evaluation_set = request().evaluation_set |
| 881 | + ledger = BudgetLedger( |
| 882 | + [ |
| 883 | + EvaluationBudget( |
| 884 | + backend_id="default", |
| 885 | + evaluation_set_key=evaluation_set.budget_key("default"), |
| 886 | + total_runs=2, |
| 887 | + ) |
| 888 | + ], |
| 889 | + path=tmp_path / "budgets.json", |
| 890 | + ) |
| 891 | + ledger.save() |
| 892 | + |
| 893 | + async def failing_refund(*args, **kwargs): |
| 894 | + raise OSError("durable refund write failed") |
| 895 | + |
| 896 | + monkeypatch.setattr(ledger, "refund", failing_refund) |
| 897 | + |
| 898 | + engine = EvaluationEngine( |
| 899 | + evaluator=evaluator(tmp_path, workspace), |
| 900 | + backends=BackendRegistry({"default": backend}), |
| 901 | + database=EvaluationDatabase(id="session"), |
| 902 | + database_path=tmp_path / "database.json", |
| 903 | + budget_ledger=ledger, |
| 904 | + authorization_resolver=allow_all_evaluations, |
| 905 | + ) |
| 906 | + |
| 907 | + with pytest.raises(EvaluationInfrastructureError) as raised: |
| 908 | + await engine.evaluate_record(backend_id="default", request=request()) |
| 909 | + assert isinstance(raised.value.__cause__, OSError) |
| 910 | + |
| 911 | + |
851 | 912 | @pytest.mark.asyncio |
852 | 913 | async def test_cancelled_evaluation_is_terminal_indexed_and_refunded(tmp_path: Path): |
853 | 914 | class BlockingBackend(StubBackend): |
|
0 commit comments