104 lines
6.0 KiB
Python
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']
|