"""Integration checks use real bench workflows and deterministic responses.""" import importlib.util import json from pathlib import Path import subprocess import sys import pytest from infospace_bench import register_artifact from infospace_bench.workflow import FixtureAssistedGenerationAdapter REPO = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO / "scripts")) spec = importlib.util.spec_from_file_location("research_bench", REPO / "scripts/research_bench.py") bench = importlib.util.module_from_spec(spec) spec.loader.exec_module(bench) FIXTURES = REPO / "research/bench/fixtures" def initialize(tmp_path): root = bench.initialize(FIXTURES / "source.md", tmp_path, "synthetic-probe") (root / "profile/execution.json").unlink() # Exercise historical four-call compatibility. return root def adapter(): return FixtureAssistedGenerationAdapter.from_file(FIXTURES / "responses.yaml") def test_pipeline_carries_source_and_previous_outputs_into_review(tmp_path): root = initialize(tmp_path) delegate = adapter() prompts = {} class Capture: def generate(self, request): prompts[request.stage_id] = request.prompt return delegate.generate(request) result = bench.execute(root, Capture()) assert result["provider_calls_attempted"] == 4 assert result["status"] == "completed" assert "server can reject submission" in prompts["critique"] assert "# Synthetic claims" in prompts["critique"] assert "# Local Draft State" in prompts["critique"] assert "only P01 exists" in prompts["critique"] assert "{{" not in prompts["critique"] assert (root / "output/evaluations/source-review.md").exists() index = (root / "artifacts/index.yaml").read_text() assert "provider: fixture" in index assert result["review_status"] == "unreviewed" assert result["evidence_kind"] == "synthetic" assert not list((root / "artifacts/entities").glob("*.md")) assert not (root / "patterns").exists() with pytest.raises(ValueError, match="already attempted"): bench.execute(root, Capture()) def test_mutated_source_and_multiple_sources_are_rejected(tmp_path): root = initialize(tmp_path) source = root / "artifacts/sources/source.md" original = source.read_bytes() source.write_text("changed") with pytest.raises(ValueError, match="Source changed"): bench.plan(root) source.write_bytes(original) register_artifact(root, artifact_id="source/second.md", path=source, kind="source", title="Second") with pytest.raises(ValueError, match="Exactly one"): bench.plan(root) def test_call_cap_prevents_provider_use(tmp_path): root = initialize(tmp_path) class NeverCall: def generate(self, request): pytest.fail("Provider must not be called") with pytest.raises(ValueError, match="exceed"): bench.execute(root, NeverCall(), max_calls=3) assert not (root / "reports/research-run.json").exists() def test_failure_is_recorded_and_cannot_be_silently_retried(tmp_path): root = initialize(tmp_path) class Fail: def generate(self, request): raise RuntimeError("fixture failure") with pytest.raises(RuntimeError): bench.execute(root, Fail()) receipt = json.loads((root / "reports/research-run.json").read_text()) assert receipt["status"] == "failed" assert receipt["provider_calls_attempted"] == 1 with pytest.raises(ValueError, match="already attempted"): bench.execute(root, adapter()) def test_provider_diagnostic_keeps_status_but_not_sensitive_detail(tmp_path): from infospace_bench.errors import InfospaceError root = initialize(tmp_path) class Denied: model = "example/model" def generate(self, request): raise InfospaceError("provider_failed", "Request rejected", { "error": "HTTP Error 401: Unauthorized", "headers": {"Authorization": "Bearer TEST-SECRET-MUST-NOT-PERSIST"}, }) with pytest.raises(InfospaceError): bench.execute(root, Denied()) text = (root / "reports/research-run.json").read_text() result = json.loads(text) assert result["http_status"] == 401 assert result["model"] == "example/model" assert "TEST-SECRET" not in text def test_unidentifiable_source_is_rejected_before_creating_workspace(tmp_path): source = tmp_path / "unidentified.md" source.write_text("# No bibliography") with pytest.raises(ValueError, match="frontmatter"): bench.initialize(source, tmp_path, "invalid") assert not (tmp_path / "infospaces/invalid").exists() def test_negative_extraction_is_a_valid_result(tmp_path): from infospace_bench.workflow import AssistedGenerationResult root = initialize(tmp_path) class Negative: def generate(self, request): return AssistedGenerationResult( markdown="# No supported candidates\n\nInsufficient synthetic evidence.\n", provider="fixture", ) assert bench.execute(root, Negative())["status"] == "completed" assert not list((root / "artifacts/entities").glob("*.md")) def test_cli_refuses_fixture_output_on_real_source(tmp_path): source = tmp_path / "source.md" source.write_text((FIXTURES / "source.md").read_text().replace( "evidence_kind: synthetic", "evidence_kind: primary")) root = bench.initialize(source, tmp_path, "primary-probe") result = subprocess.run([ sys.executable, str(REPO / "scripts/research_bench.py"), "run", str(root), "--fixture-responses", str(FIXTURES / "responses.yaml"), ], capture_output=True, text=True) assert result.returncode != 0 assert "only run against synthetic" in result.stderr assert not (root / "reports/research-run.json").exists() @pytest.mark.parametrize('finish,content,complete', [ ('stop', '# Supported claim\n\nProvisional.', True), ('length', '# Cut short', False), ('stop', '', False), ]) def test_llm_connect_service_contract(tmp_path, finish, content, complete): from llm_connect.models import LLMResponse from llm_connect.server import LLMServer calls = [] class ServiceAdapter: def validate_config(self, config): return True def execute_prompt(self, prompt, config): calls.append((prompt, config)) return LLMResponse(content=content, model='resolved/model', usage={'total_tokens': 42}, finish_reason=finish, metadata={'provider': 'test', 'response_id': 'r1', 'debug': 'DO-NOT-PERSIST'}) server = LLMServer(ServiceAdapter(), port=0) server.start() root = initialize(tmp_path) try: live = bench.LLMConnectAdapter(f'http://127.0.0.1:{server.port}', 'requested/model', reasoning_effort='low') if complete: assert bench.execute(root, live)['status'] == 'completed' assert len(calls) == 4 else: with pytest.raises(ValueError, match='empty or incomplete'): bench.execute(root, live) assert len(calls) == 1 assert (root / 'reports/rejected-response.md').read_text() == content receipt_text = (root / 'reports/research-run.json').read_text() receipt = json.loads(receipt_text) assert receipt['responses'][0]['model'] == 'resolved/model' assert receipt['responses'][0]['usage']['total_tokens'] == 42 assert receipt['responses'][0]['finish_reason'] == finish assert receipt['responses'][0]['content_characters'] == len(content) assert receipt['responses'][0]['content_sha256'] is not None assert receipt['service_requests_attempted'] == len(calls) assert calls[0][1].model_name == 'requested/model' assert calls[0][1].max_tokens == 4000 assert calls[0][1].model_params['reasoning_effort'] == 'low' assert 'DO-NOT-PERSIST' not in receipt_text finally: server.stop() def test_llm_connect_http_failure_is_safe_and_not_retried(tmp_path): from llm_connect.exceptions import LLMAPIError from llm_connect.server import LLMServer calls = [] class FailingService: def validate_config(self, config): return True def execute_prompt(self, prompt, config): calls.append(prompt) raise LLMAPIError('SECRET-SENTINEL', status_code=401, response_body='SECRET-SENTINEL') server = LLMServer(FailingService(), port=0) server.start() root = initialize(tmp_path) try: live = bench.LLMConnectAdapter(f'http://127.0.0.1:{server.port}', 'test') with pytest.raises(bench.InfospaceError) as raised: bench.execute(root, live) text = (root / 'reports/research-run.json').read_text() assert len(calls) == 1 assert json.loads(text)['http_status'] >= 400 assert 'SECRET-SENTINEL' not in text + str(raised.value) finally: server.stop() def test_diagnostics_allowlist_excludes_raw_provider_data(): raw = {'debug': {'provider_request': {'headers': {'Authorization': 'SECRET'}, 'payload': {'messages': ['SECRET'], 'max_tokens': 1000, 'reasoning_effort': 'low'}}, 'provider_response': {'body': {'choices': ['SECRET'], 'usage': { 'total_tokens': 1200, 'cost': 0.01, 'private': 'SECRET', 'completion_tokens_details': {'reasoning_tokens': 900, 'private': 'SECRET'}}}}}} summary = bench.diagnostic_summary(raw) assert summary['effective_settings']['reasoning_effort_present'] assert summary['provider_usage']['completion_tokens_details']['reasoning_tokens'] == 900 assert 'SECRET' not in json.dumps(summary) def test_current_llm_connect_drops_reasoning_effort(): from llm_connect._payload import merge_openai_chat_model_params payload = {'max_tokens': 1000} merge_openai_chat_model_params(payload, {'reasoning_effort': 'low'}) assert 'reasoning_effort' not in payload def conditional_root(tmp_path, slug='conditional'): return bench.initialize(FIXTURES / 'source.md', tmp_path, slug) def structured_adapter(count=1, edges=None, malformed=False): from infospace_bench.workflow import AssistedGenerationResult candidates = {'candidates': [ {'id': f'P{i+1:02}', 'known_uses': [], 'recurrence': 'unknown'} for i in range(count)]} return FixtureAssistedGenerationAdapter({ ('claims', '*'): AssistedGenerationResult('# Synthetic claims', provider='fixture'), ('candidates', '*'): AssistedGenerationResult( 'broken' if malformed else '```json\n' + json.dumps(candidates) + '\n```', provider='fixture'), ('relations', '*'): AssistedGenerationResult( '```json\n' + json.dumps(edges or []) + '\n```', provider='fixture'), }) @pytest.mark.parametrize('count,expected', [(0, 2), (1, 2), (2, 3)]) def test_conditional_stage_call_counts(tmp_path, count, expected): root = conditional_root(tmp_path) result = bench.execute(root, structured_adapter(count), max_calls=expected) assert result['status'] == 'completed' assert result['provider_calls_attempted'] == expected assert result['deterministic_stages'] == 4 - expected assert 'Editorial review required' in (root / 'output/evaluations/source-review.md').read_text() assert result['editorial_corrections'] is None assert result['review_status'] == 'unreviewed' def test_invalid_candidates_stop_before_relations(tmp_path): root = conditional_root(tmp_path) with pytest.raises(ValueError, match='candidates structural screen'): bench.execute(root, structured_adapter(malformed=True)) receipt = json.loads((root / 'reports/research-run.json').read_text()) assert receipt['provider_calls_attempted'] == 2 assert receipt['rejected_assertions'] > 0 assert (root / 'reports/rejected-stage.json').exists() assert not (root / 'artifacts/generated/relation-proposals.md').exists() def test_invalid_edges_stop_before_editorial_stage(tmp_path): root = conditional_root(tmp_path) with pytest.raises(ValueError, match='relations structural screen'): bench.execute(root, structured_adapter(2, [{'subject_candidate_id': 'P01', 'object_candidate_id': 'C01', 'relation': 'requires'}])) receipt = json.loads((root / 'reports/research-run.json').read_text()) assert receipt['provider_calls_attempted'] == 3 assert not (root / 'output/evaluations/source-review.md').exists() def test_explicit_reuse_makes_no_new_calls(tmp_path): first = conditional_root(tmp_path, 'first') bench.execute(first, structured_adapter()) second = conditional_root(tmp_path, 'second') result = bench.execute(second, structured_adapter(), max_calls=0, reuse_from=first) assert result['provider_calls_attempted'] == 0 assert result['cache_hits'] == 2 assert 'reused_from' in (second / 'artifacts/index.yaml').read_text() @pytest.mark.parametrize('change', ['prompt', 'source', 'fixture']) def test_changed_inputs_never_reuse_stale_output(tmp_path, change): first = conditional_root(tmp_path, 'first') bench.execute(first, structured_adapter()) source = FIXTURES / 'source.md' if change == 'source': source = tmp_path / 'different.md' source.write_text((FIXTURES / 'source.md').read_text() + '\nNew constraint.\n') second = bench.initialize(source, tmp_path, 'second') if change == 'prompt': template = second / 'profile/templates/claims.md' template.write_text(template.read_text() + '\nChanged question.\n') with pytest.raises(ValueError, match='call limit'): bench.execute(second, structured_adapter(2 if change == 'fixture' else 1), max_calls=0, reuse_from=first) receipt = json.loads((second / 'reports/research-run.json').read_text()) assert receipt['cache_hits'] == 0 assert receipt['provider_calls_attempted'] == 0 def test_modified_cache_and_failed_run_are_not_reusable(tmp_path): first = conditional_root(tmp_path, 'first') bench.execute(first, structured_adapter()) cache = first / 'reports/stage-cache.json' cache.write_text('{}') second = conditional_root(tmp_path, 'second') with pytest.raises(ValueError, match='matching cache digest'): bench.execute(second, structured_adapter(), reuse_from=first) assert not (second / 'reports/research-run.json').exists() def test_two_candidate_relation_survives_screen(tmp_path): root = conditional_root(tmp_path) edge = {'subject_candidate_id': 'P01', 'object_candidate_id': 'P02', 'relation': 'complements', 'rationale': 'Synthetic complement', 'source_claim_and_locator': 'C01 Scenario', 'counterexample': 'No persistence requirement', 'observation_versus_hypothesis': 'hypothesis'} result = bench.execute(root, structured_adapter(2, [edge])) assert result['provider_calls_attempted'] == 3 assert result['status'] == 'completed' def test_service_reuse_and_setting_invalidation(tmp_path): from llm_connect.models import LLMResponse from llm_connect.server import LLMServer calls = [] class Service: def validate_config(self, config): return True def execute_prompt(self, prompt, config): calls.append(prompt) return LLMResponse(content='```json\n{"candidates": []}\n```', model='test', usage={'total_tokens': 42}) server = LLMServer(Service(), port=0) server.start() endpoint = f'http://127.0.0.1:{server.port}' try: first = conditional_root(tmp_path, 'first') r = bench.execute(first, bench.LLMConnectAdapter(endpoint, 'test')) assert r['reported_tokens_this_run'] == 84 second = conditional_root(tmp_path, 'second') r = bench.execute(second, bench.LLMConnectAdapter(endpoint, 'test'), max_calls=0, reuse_from=first) assert r['reported_tokens_this_run'] == 0 assert r['service_requests_attempted'] == 0 assert len(calls) == 2 third = conditional_root(tmp_path, 'third') with pytest.raises(ValueError, match='call limit'): bench.execute(third, bench.LLMConnectAdapter(endpoint, 'different-model'), max_calls=0, reuse_from=first) receipt = json.loads((first / 'reports/research-run.json').read_text()) receipt['status'] = 'failed' (first / 'reports/research-run.json').write_text(json.dumps(receipt)) fourth = conditional_root(tmp_path, 'fourth') with pytest.raises(ValueError, match='completed run'): bench.execute(fourth, bench.LLMConnectAdapter(endpoint, 'test'), reuse_from=first) finally: server.stop()