Files
VianoBugAnalysis/tests/test_investigation_view.py

104 lines
6.0 KiB
Python

import copy
import pytest
from types import SimpleNamespace
from viano_bug_analysis.analysis.investigation_view import working_view
from viano_bug_analysis.analysis.investigator import Investigator
def context():
proposal={'title':'A','mechanism':'May be absent','distinction':'Static versus dynamic',
'falsifying_evidence':['A present object would disconfirm absence'],
'expected_evidence':['No rows'],'open_questions':['Exists?'],'rationale':'Untested',
'source_ids':['F0029'],'interpretation_ids':['I0001']}
result={'record_ref':'evidence/one.json','record':{'id':'E000001','hypothesis_id':'H1',
'action_id':'A1','novel_atoms':['internal-hash']},
'content':{'items':[{'row':{'entry':23487,'name':'Wild subject'}}],
'scope':'map 0','coverage':'complete_filtered_rows','truncated':False}}
return {'case':{'case':{'case_id':'case','package_id':'package','analysis_revision':1},
'inputs':{'huge':'not needed'},'source_context':{'refs':[{'path':'original/'+'a'*64+'/code.cpp'}]},
'database_context':{'refs':[]}},
'case_sources':{'F0029':{'kind':'fact','data':{'name':'entity_name_lookup','value':{'entry':23487}}},
'O0001':{'kind':'observation','data':{'text':'Nothing visible','provenance':{'author':'Tester'}}},
'X0001':{'kind':'unread_reference','data':{'path':'original/'+'a'*64+'/code.cpp','size':100}}},
'hypotheses':{h:{'proposal':copy.deepcopy(proposal),'notes':['Counterevidence matters'],
'questions':[{'id':'Q1','text':'Exists?','resolved':False}],
'confidence':'unassessed','evidence_refs':['E000001']} for h in ['H1','H2']},
'selected_hypothesis':'H1','budget':{'seen_evidence':['hash'], 'accounts':{'H1':{
'remaining':90,'spent':10,'rewarded':0,'status':'investigating','actions':{
'A1':{'status':'settled'},'A2':{'status':'result_stored','cost':10}}}}},
'evidence':{'E000001':result,'E000002':{**copy.deepcopy(result),'record':{
**result['record'],'id':'E000002','hypothesis_id':'H2'}}},
'previous_result':result,'tool_schema':{'x-project-bindings':{'database':{'snapshot_id':'snapshot'}}}}
def test_working_view_preserves_sources_counterevidence_and_pending_budget():
c=context();before=copy.deepcopy(c);aliases={}
view=working_view(c,aliases_out=aliases)
assert c==before
assert 'inputs' not in view['case']
assert view['sources']['F0029']['data']==c['case_sources']['F0029']['data']
assert view['sources']['O0001']==c['case_sources']['O0001']
assert set(view['unread_sources'])=={'X0001'}
for eid in c['evidence']:
assert view['evidence'][eid]['content']==c['evidence'][eid]['content']
assert 'novel_atoms' not in view['evidence'][eid]['record']
assert view['previous_result']=={'evidence_id':'E000001','see':'evidence'}
assert view['hypotheses']['H2']['proposal']['falsifying_evidence']==c['hypotheses']['H2']['proposal']['falsifying_evidence']
assert view['hypotheses']['H2']['notes']==c['hypotheses']['H2']['notes']
account=view['budget']['accounts']['H1']
assert account['remaining']==90 and account['spent']==10
assert account['actions']=={'A2':{'status':'result_stored','cost':10}}
assert account['settled_action_count']==1
assert 'seen_evidence' not in view['budget']
assert aliases[view['unread_sources']['X0001']['data']['path']]==c['case_sources']['X0001']['data']['path']
assert working_view(c)==view
@pytest.mark.parametrize('version',[None,'investigation_project_v11'])
def test_tool_reference_alias_resolution_is_scoped_and_preserves_model_decision(monkeypatch,version):
c=context()
if version:c['prompt_version']=version
aliases={};view=working_view(c,aliases_out=aliases)
alias=view['unread_sources']['X0001']['data']['path']
state={'case_id':'case','package_id':'package','analysis_revision':1,'prompt_protocol':2,
'active':{'hypothesis_id':'H1','iteration':1,'context_ref':'context.json'}}
class Reader:
def __init__(self,*args):pass
def __enter__(self):return self
def __exit__(self,*args):pass
monkeypatch.setattr('viano_bug_analysis.analysis.investigator.PackageReader',Reader)
fake=SimpleNamespace(store=SimpleNamespace(limits=None),_latest=lambda *args:'package.zip',
_read=lambda *args:c)
action={'operation':'source.find_text','arguments':{'source_refs':[alias,'UNKNOWN_ALIAS'],'text':'needle'}}
original=copy.deepcopy(action)
request=Investigator._tool_request(fake,state,action)
assert action==original
assert request['requests'][0]['arguments']['source_refs']==[c['case_sources']['X0001']['data']['path'],'UNKNOWN_ALIAS']
def test_unlisted_file_inventory_is_explicit_not_a_negative_finding():
c=context()
c['case_sources']['X0002']={'kind':'unread_reference','data':{'path':'other/raw.json','size':50}}
v=working_view(c)
assert 'X0002' in v['unread_inventory_scope']['unlisted_source_ids']
assert 'X0002' not in v['unread_sources']
assert v['sources']['F0029']['data']['value']['entry']==23487
assert v['evidence']['E000002']['content']==c['evidence']['E000002']['content']
def test_attribution_is_preserved_without_archive_navigation_scaffolding():
c=context()
c['case_sources']['O0001']['data']['provenance']={
'kind':'tester','author':'Tester','observed_at':'2026-10-08T13:00:00Z',
'source_ref':'original/'+'a'*64+'/report.json',
'origin':{'source_timestamp':'2026-10-08T12:59:00Z','json_pointer':'/report/0',
'archive_ref':'original/'+'b'*64+'/archive.tar.gz'}}
c['tool_schema']['x-project-bindings']['query_schema']={'duplicated':'contract'}
v=working_view(c)
p=v['sources']['O0001']['data']['provenance']
assert p=={'kind':'tester','author':'Tester','observed_at':'2026-10-08T13:00:00Z',
'origin':{'source_timestamp':'2026-10-08T12:59:00Z'}}
assert 'query_schema' not in v['tool_schema']['project']
assert 'source_ref' in c['case_sources']['O0001']['data']['provenance']