11"""Offline oracles for the opt-in model evaluation; no credentials or paid calls."""
22import importlib .util
3+ import json
34from pathlib import Path
5+ import sys
6+
7+ import pytest
48
59ROOT = Path (__file__ ).resolve ().parents [1 ]
610spec = importlib .util .spec_from_file_location ("chat_intake_eval" , ROOT / "examples/evaluations/chat-intake.py" )
@@ -25,8 +29,8 @@ def test_existing_work_requires_the_right_recipient_without_a_second_gate():
2529def test_complete_new_request_does_not_need_another_question ():
2630 case = {"id" : "new" , "expected" : "draft" , "ready" : True }
2731 draft = {"completion_criteria" : "Cited report" , "question" : "" }
28- assert evaluation .score (case , {"goal_draft" : draft })["passed" ]
29- assert not evaluation .score (case , {"goal_draft" : {** draft , "question" : "Ready to begin?" }})["passed" ]
32+ assert evaluation .score (case , {"message" : "Here is the draft." , " goal_draft" : draft })["passed" ]
33+ assert not evaluation .score (case , {"message" : "Here is the draft." , " goal_draft" : {** draft , "question" : "Ready to begin?" }})["passed" ]
3034
3135
3236def test_ordinary_question_cannot_silently_become_work ():
@@ -61,3 +65,103 @@ def test_shared_intake_guidance_precedes_handoff_without_widening_runtime():
6165 for profile in ["restricted" , "trusted_owner" ]:
6266 assert CONVERSATION_INTENT_RESOLUTION_INSTRUCTION in manager_agent_objective (profile )
6367 assert CONVERSATION_INTENT_RESOLUTION_INSTRUCTION not in _turn_prompt ("Execute." , execution_mode = True )
68+
69+
70+ @pytest .mark .parametrize ("message" , [None , "" , " \n \t " , 7 , ["Answer" ]])
71+ def test_an_ordinary_answer_needs_visible_text (message ):
72+ row = evaluation .score ({"id" : "ordinary" , "expected" : "answer" }, {"message" : message })
73+ assert not row ["passed" ] and "missing_answer" in row ["errors" ]
74+
75+
76+ @pytest .mark .parametrize ("provider" , ["codex" , "operator-api" ])
77+ @pytest .mark .parametrize ("message" , ["A sourced explanation." , "" ])
78+ def test_release_cli_scores_provider_output_and_hashes_effective_prompt (
79+ tmp_path , monkeypatch , provider , message
80+ ):
81+ """Exercise the real CLI/report flow; only provider inference is simulated."""
82+ from contextlib import contextmanager
83+ import hashlib
84+ from io import BytesIO
85+
86+ suite = {"contexts" : {"ordinary" : {"scope" : "public" , "goals" : []}},
87+ "cases" : [{"id" : "ordinary" , "context_ref" : "ordinary" , "expected" : "answer" ,
88+ "request" : "Explain\n what a Goal means." }]}
89+ cases = tmp_path / "cases.json"
90+ cases .write_text (json .dumps (suite ))
91+ output = tmp_path / "report.json"
92+ monkeypatch .setattr (sys , "argv" , ["chat-intake" , "--live" , "--provider" , provider ,
93+ "--model" , "fixture-model" , "--cases" , str (cases ), "--output" , str (output )])
94+ monkeypatch .setattr (evaluation , "operator_provider_environ" , lambda _ : {"DEEPSEEK_API_KEY" : "synthetic-test-only" })
95+ observed = []
96+ response = {"message" : message , "proposals" : [], "protected_action" : None , "gate" : None }
97+
98+ class CodexFixture :
99+ context_summary = "wrong startup context"
100+
101+ def send (self , request , * , on_event ):
102+ observed .append (evaluation ._turn_prompt (request , context_summary = self .context_summary ))
103+ on_event ("answer.final" , {"response" : response })
104+ return response
105+
106+ @contextmanager
107+ def start (** kwargs ):
108+ assert kwargs ["model" ] == "fixture-model"
109+ yield CodexFixture ()
110+
111+ def urlopen (request , ** _ ):
112+ observed .append (json .loads (request .data )["messages" ][0 ]["content" ])
113+ envelope = evaluation .CHAT_REVIEW_OPEN_TAG + json .dumps (response ) + evaluation .CHAT_REVIEW_CLOSE_TAG
114+ return BytesIO (json .dumps ({"choices" : [{"finish_reason" : "stop" , "message" : {"content" : envelope }}]}).encode ())
115+
116+ monkeypatch .setattr (evaluation .CodexChatAgentSession , "start" , start )
117+ monkeypatch .setattr (evaluation .urllib .request , "urlopen" , urlopen )
118+ assert evaluation .main () == (0 if message else 1 )
119+ report = json .loads (output .read_text ())
120+ assert report ["total" ] == len (observed ) == 1
121+ expected = evaluation ._turn_prompt ("Explain what a Goal means." , context_summary = json .dumps (suite ["contexts" ]["ordinary" ], ensure_ascii = False ))
122+ assert observed == [expected ], "Both providers must receive the same case context and request"
123+ assert report ["results" ][0 ]["prompt_sha256" ] == hashlib .sha256 (expected .encode ()).hexdigest ()
124+ assert report ["passed" ] == bool (message )
125+ assert "synthetic-test-only" not in output .read_text ()
126+
127+
128+ def test_codex_protocol_warning_fails_even_with_a_readable_fallback (tmp_path , monkeypatch , capsys ):
129+ from contextlib import contextmanager
130+
131+ cases = tmp_path / "cases.json"
132+ cases .write_text (json .dumps ({"contexts" : {"ordinary" : {}}, "cases" : [
133+ {"id" : "ordinary" , "context_ref" : "ordinary" , "expected" : "answer" , "request" : "Explain Goals." }]}))
134+ output = tmp_path / "report.json"
135+ monkeypatch .setattr (sys , "argv" , ["chat-intake" , "--live" , "--provider" , "codex" , "--model" , "fixture-model" ,
136+ "--cases" , str (cases ), "--output" , str (output )])
137+ monkeypatch .setattr (evaluation , "operator_provider_environ" , lambda _ : {})
138+
139+ class WarningFixture :
140+ def send (self , request , * , on_event ):
141+ on_event ("protocol.warning" , {"error_code" : "missing_review_envelope" , "detail" : "do-not-persist-provider-payload" })
142+ return {"message" : "A readable fallback that is not protocol-qualified." }
143+
144+ @contextmanager
145+ def start (** _ ):
146+ yield WarningFixture ()
147+
148+ monkeypatch .setattr (evaluation .CodexChatAgentSession , "start" , start )
149+ assert evaluation .main () == 1
150+ row = json .loads (output .read_text ())["results" ][0 ]
151+ assert row ["errors" ] == ["protocol_warning" ] and not row ["passed" ]
152+ assert "do-not-persist-provider-payload" not in output .read_text () + capsys .readouterr ().out
153+
154+
155+ def test_release_cli_without_live_never_reads_credentials_or_calls_provider (tmp_path , monkeypatch ):
156+ monkeypatch .setattr (sys , "argv" , ["chat-intake" , "--model" , "fixture-model" , "--output" , str (tmp_path / "report.json" )])
157+
158+ def forbidden (* _ , ** __ ):
159+ pytest .fail ("No provider or credential access without explicit live opt-in" )
160+
161+ monkeypatch .setattr (evaluation , "operator_provider_environ" , forbidden )
162+ monkeypatch .setattr (evaluation .CodexChatAgentSession , "start" , forbidden )
163+ monkeypatch .setattr (evaluation .urllib .request , "urlopen" , forbidden )
164+ with pytest .raises (SystemExit ) as error :
165+ evaluation .main ()
166+ assert error .value .code == 2
167+ assert not (tmp_path / "report.json" ).exists ()
0 commit comments