461 lines
25 KiB
Python
461 lines
25 KiB
Python
import asyncio
|
|
import copy
|
|
import json
|
|
import pytest
|
|
from jsonschema import Draft202012Validator
|
|
from viano_bug_analysis.analysis import CaseInterpreter,HypothesisConfig
|
|
from viano_bug_analysis.analysis.joint_v2 import VERSION,REPAIR
|
|
from viano_bug_analysis.llm import ProviderResponse
|
|
from viano_bug_analysis.package import PackageReader
|
|
from test_case_builder import case_setup
|
|
from test_case_interpreter import built_case,payload,gateway
|
|
from test_joint_planning import JointAdapter,hypotheses
|
|
|
|
|
|
class Adapter(JointAdapter):
|
|
def __init__(self, change=None, review=None):
|
|
super().__init__(change);self.review=review
|
|
async def generate(self,config,request,**kwargs):
|
|
if request['prompt_version']!=REPAIR:return await super().generate(config,request,**kwargs)
|
|
self.requests.append(copy.deepcopy(request))
|
|
wire=json.loads(request['messages'][1]['content'])
|
|
from viano_bug_analysis.analysis.json_consolidation import restore
|
|
body=restore(wire['repair'])
|
|
patches=[]
|
|
for issue in body['issues']:
|
|
path=issue['path'];parts=path.strip('/').split('/')
|
|
value=payload()[parts[0]]
|
|
if len(parts)>1:value=value[int(parts[1])]
|
|
patches.append({'path':path,'replacement':copy.deepcopy(value)})
|
|
reviews=[{'position':i,'decision':'retain','reason':'Neither direct inputs nor implicit causal prerequisites rely on the corrected statement.','replacement':None} for i,_ in enumerate((body['hypotheses'] or {}).get('hypotheses',[]))]
|
|
out={'patches':patches,'reviews':reviews}
|
|
if self.review:self.review(out,body)
|
|
return ProviderResponse(out,'fixture',100,50)
|
|
|
|
|
|
def interpret(store,gw):
|
|
from viano_bug_analysis.analysis.correction import CorrectionConfig
|
|
return asyncio.run(CaseInterpreter(store,gw,joint_config=HypothesisConfig(),correction_config=CorrectionConfig()).interpret('case-A','stable-package'))
|
|
|
|
|
|
def test_source_enums_and_explicit_assumptions(built_case):
|
|
adapter=Adapter();store=built_case[0]
|
|
result=interpret(store,gateway(adapter));assert result.status=='COMPLETED'
|
|
req=adapter.requests[0];assert req['prompt_version']==VERSION
|
|
body=json.loads(req['messages'][1]['content']);schema=body['block_contracts']['interpretation']
|
|
obs=schema['properties']['observed_behavior']['items']['properties']['source_ids']['items']['enum']
|
|
assert 'O0001' in obs and 'F0001' not in obs
|
|
assert 'premises' in body['block_contracts']['hypotheses']['$defs']['proposal']['required']
|
|
assert hypotheses(store,gateway(adapter)).status=='COMPLETED'
|
|
assert len(adapter.requests)==1
|
|
|
|
|
|
def test_statement_repair_retains_independent_hypotheses_and_replays(built_case):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
fixed=interpret(store,gw);assert fixed.status=='COMPLETED'
|
|
assert adapter.requests[-1]['prompt_version']==REPAIR
|
|
assert 'input' not in json.loads(adapter.requests[-1]['messages'][1]['content'])
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).reused
|
|
assert interpret(store,gw).reused
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
def test_implicit_uncertainty_locks_other_candidates_in_followup(built_case):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
def review(d,_):d['reviews'][1]['decision']='uncertain'
|
|
store=built_case[0];adapter=Adapter(bad,review);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
result=hypotheses(store,gw);assert result.status=='REJECTED'
|
|
with PackageReader(result.package) as r:
|
|
c=json.loads(r.read_bytes(r.manifest['artifacts']['hypothesis_context']))
|
|
assert set(c['joint_retained']['proposals'])=={'0','2'}
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert adapter.requests[-1]['output_schema']['properties']['hypotheses']['prefixItems'][0].get('const')
|
|
assert len(adapter.requests)==3
|
|
|
|
|
|
def test_changed_direct_input_cannot_be_retained(built_case):
|
|
def bad(d):
|
|
d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
d['hypotheses']['hypotheses'][1]['interpretation_paths']=['/interpretation/observed_behavior/0']
|
|
d['hypotheses']['hypotheses'][1]['premises'][0]['interpretation_paths']=['/interpretation/observed_behavior/0']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).code=='HYPOTHESIS_DEPENDENCY_CHANGED'
|
|
|
|
|
|
def test_unlisted_patch_is_rejected(built_case):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
def review(d,_):d['patches'][0]['path']='/confirmed_technical_facts/0'
|
|
store=built_case[0];adapter=Adapter(bad,review);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
with PackageReader(store.latest('case-A','stable-package')) as r:assert not r.manifest['artifacts'].get('case_interpretation')
|
|
|
|
|
|
def test_rejected_corpus_produces_local_issues_without_modifying_original():
|
|
from pathlib import Path
|
|
from viano_bug_analysis.analysis.joint_v2 import issues
|
|
root=Path('tests/fixtures/llm_rejections/20261019/joint-interpretation-source-kinds')
|
|
context=json.loads((root/'validation-context.json').read_text(encoding='utf-8'))
|
|
context['joint_generation']['version']=2
|
|
data=json.loads((root/'parsed-answer.json').read_text(encoding='utf-8'))
|
|
before=copy.deepcopy(data)
|
|
found=issues(data['interpretation'],context)
|
|
assert {'/observed_behavior/2','/tester_assumptions'}<={i['path'] for i in found}
|
|
assert data==before
|
|
|
|
|
|
def test_bug21_reports_independent_description_and_identity_errors_together():
|
|
from pathlib import Path
|
|
from viano_bug_analysis.analysis.joint_v2 import issues
|
|
from viano_bug_analysis.analysis.contracts import validate_interpretation
|
|
from viano_bug_analysis.analysis.errors import InterpretationError
|
|
root=Path('tests/fixtures/llm_rejections/20261024/bug21-independent-citation-errors')
|
|
context=json.loads((root/'validation-context.json').read_text(encoding='utf-8'))
|
|
original=json.loads((root/'parsed-answer.json').read_text(encoding='utf-8'))
|
|
before=copy.deepcopy(original)
|
|
found=issues(original,context)
|
|
assert {'/relevant_entities/1','/described_behavior/0'} <= {i['path'] for i in found}
|
|
assert '/described_behavior/0' not in {i['path'] for i in issues(original,context,comprehensive=False)}
|
|
fixed=copy.deepcopy(original)
|
|
fixed['relevant_entities'][1]['source_ids'].append('N0002')
|
|
with pytest.raises(InterpretationError):validate_interpretation(fixed,context)
|
|
fixed['described_behavior'][0]['source_ids'].remove('B0002')
|
|
validate_interpretation(fixed,context)
|
|
assert original==before
|
|
|
|
|
|
def test_failed_analysis_does_not_claim_successful_handoff():
|
|
from viano_bug_analysis.api.cases import next_actions
|
|
failed=next_actions('FAILED_TO_ANALYZE','MODEL_VALIDATION_EXHAUSTED',{})
|
|
assert 'ohne erfolgreich validierten Abschluss' in failed[0]['text']
|
|
assert 'Review-Rückmeldung' in failed[1]['text']
|
|
finished=next_actions('OUTPUT_READY',None,{})
|
|
assert 'Aufträge und Fallakte' in finished[0]['text']
|
|
|
|
|
|
def test_new_feedback_uses_frozen_comprehensive_diagnostics_and_legacy_replays(monkeypatch):
|
|
from pathlib import Path
|
|
from viano_bug_analysis.analysis import correction,joint_planning,joint_v2
|
|
root=Path('tests/fixtures/llm_rejections/20261024/bug21-independent-citation-errors')
|
|
context=json.loads((root/'validation-context.json').read_text(encoding='utf-8'))
|
|
payload_=json.loads((root/'parsed-answer.json').read_text(encoding='utf-8'))
|
|
monkeypatch.setattr(correction,'read_json',lambda *a,**kw:context)
|
|
monkeypatch.setattr(joint_planning,'interpretation_payload',lambda *a,**kw:payload_)
|
|
call={'outcome':{'document':payload_},'prompt_version':'case_plan_interpretation_repair_v1'}
|
|
error={'context_ref':'context','code':'INTERPRETATION_ENTITY_PROVENANCE',
|
|
'prompt_version':'case_plan_interpretation_repair_v1'}
|
|
legacy=correction.feedback(None,'interpretation',('error',error,call))
|
|
assert legacy['issues']==joint_v2.issues(payload_,context,comprehensive=False)
|
|
frozen=joint_v2.issues(payload_,context)
|
|
current=correction.feedback(None,'interpretation',('error',{**error,'validation_issues':frozen},call))
|
|
assert current['issues']==frozen
|
|
assert {'/relevant_entities/1','/described_behavior/0'} <= {i['path'] for i in current['issues']}
|
|
|
|
|
|
def test_direct_dependency_can_be_explicitly_replaced(built_case):
|
|
def bad(d):
|
|
d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
h=d['hypotheses']['hypotheses'][1]
|
|
h['interpretation_paths']=['/interpretation/observed_behavior/0']
|
|
h['premises'][0]['interpretation_paths']=h['interpretation_paths']
|
|
def review(d,body):
|
|
d['reviews'][1].update(decision='replace',replacement={'hypotheses':[copy.deepcopy(body['hypotheses']['hypotheses'][1])]})
|
|
store=built_case[0];adapter=Adapter(bad,review);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
def test_premise_cannot_invent_an_input_reference(built_case):
|
|
def bad(d):d['hypotheses']['hypotheses'][0]['premises'][0]['source_ids']=['F9999']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).code=='HYPOTHESIS_PREMISE_SOURCE'
|
|
with PackageReader(store.latest('case-A','stable-package')) as r:
|
|
assert r.manifest['artifacts']['case_interpretation']
|
|
assert not r.manifest['artifacts'].get('hypotheses')
|
|
|
|
|
|
def test_followup_cannot_rewrite_retained_candidates(built_case):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
def review(d,_):d['reviews'][1]['decision']='uncertain'
|
|
store=built_case[0];adapter=Adapter(bad,review);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED';assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='REJECTED'
|
|
from test_hypothesis_builder import ProposalAdapter
|
|
wrong=ProposalAdapter(change=lambda d,c:d['hypotheses'][0].update(title='Changed retained candidate'))
|
|
result=hypotheses(store,gateway(wrong));assert result.status=='REJECTED'
|
|
with PackageReader(result.package) as r:assert not r.manifest['artifacts'].get('hypotheses')
|
|
|
|
|
|
def test_patch_call_reused_after_interrupted_publication(built_case,monkeypatch):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
original=store.advance
|
|
def fail(*args,**kwargs):
|
|
if kwargs.get('details',{}).get('operation')=='case-interpreted':raise RuntimeError('simulated crash')
|
|
return original(*args,**kwargs)
|
|
monkeypatch.setattr(store,'advance',fail)
|
|
from viano_bug_analysis.analysis.errors import InterpretationError
|
|
with pytest.raises(InterpretationError,match='INTERPRETATION_INPUT'):interpret(store,gw)
|
|
assert len(adapter.requests)==2
|
|
monkeypatch.setattr(store,'advance',original)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
@pytest.mark.parametrize('broken',[None,17,'broken',{'text':'bad','source_ids':17}])
|
|
def test_issue_localization_handles_malformed_model_fields(built_case,broken):
|
|
def bad(d):d['interpretation']['unknowns']=broken
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
result=interpret(store,gw);assert result.status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
|
|
|
|
def test_repaired_plan_reimports_without_new_calls(built_case,tmp_path):
|
|
from viano_bug_analysis.lifecycle import Workspace,CheckpointStore
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED';assert interpret(store,gw).status=='COMPLETED'
|
|
final=hypotheses(store,gw)
|
|
imported=CheckpointStore(Workspace(tmp_path/'recipient'))
|
|
imported.intake(final.package)
|
|
assert interpret(imported,gw).reused
|
|
assert hypotheses(imported,gw).reused
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
def test_budget_origin_validates_the_reviewed_source_chain(built_case):
|
|
from viano_bug_analysis.budget import BudgetManager
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED';assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
result=BudgetManager(store).initialize('case-A','stable-package')
|
|
assert len(result.state['accounts'])==3
|
|
assert {v['remaining'] for v in result.state['accounts'].values()}=={100}
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
@pytest.mark.parametrize('malformed',[None,17,[{'position':0}],'invalid'])
|
|
def test_bad_review_does_not_discard_valid_interpretation_patch(built_case,malformed):
|
|
def bad(d):d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
def review(d,_):d['reviews']=malformed
|
|
store=built_case[0];adapter=Adapter(bad,review);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='REJECTED'
|
|
with PackageReader(store.latest('case-A','stable-package')) as r:assert r.manifest['artifacts']['case_interpretation']
|
|
|
|
|
|
def test_valid_premise_dependencies_are_unioned_without_mutating_answer(built_case):
|
|
def missing(d):
|
|
h=d['hypotheses']['hypotheses'][0]
|
|
h['premises'][0]['interpretation_paths'].append('/interpretation/observed_behavior/0')
|
|
h['interpretation_paths']=['/interpretation/unknowns/0']
|
|
store=built_case[0];adapter=Adapter(missing);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert len(adapter.requests)==1
|
|
|
|
|
|
def test_single_invalid_candidate_locks_valid_siblings(built_case):
|
|
def bad(d):d['hypotheses']['hypotheses'][1]['premises'][0]['source_ids']=['F9999']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
result=hypotheses(store,gw);assert result.code=='HYPOTHESIS_PREMISE_SOURCE'
|
|
with PackageReader(result.package) as r:
|
|
c=json.loads(r.read_bytes(r.manifest['artifacts']['hypothesis_context']))
|
|
assert set(c['joint_retained']['proposals'])=={'0','2'}
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
assert len(adapter.requests)==2
|
|
|
|
|
|
def test_real_dependency_corpus_normalization_is_lossless_and_bounded():
|
|
from pathlib import Path
|
|
import hashlib
|
|
from viano_bug_analysis.analysis.joint_v2 import complete_dependencies,validate_premises
|
|
root=Path('tests/fixtures/llm_rejections/20261020/joint-v2-dependencies')
|
|
for name,digest in json.loads((root/'hashes.json').read_text(encoding='utf-8')).items():
|
|
assert hashlib.sha256((root/name).read_bytes()).hexdigest()==digest
|
|
context=json.loads((root/'context.json').read_text(encoding='utf-8'))
|
|
accepted=json.loads((root/'case_interpretation.json').read_text(encoding='utf-8'))['interpretation']
|
|
doc=json.loads((root/'case_plan_v2.json').read_text(encoding='utf-8'))['outcome']['document']
|
|
raw=doc['hypotheses'];before=copy.deepcopy(raw)
|
|
context['joint_generation']['recovery_version']=1
|
|
fixed=complete_dependencies(raw,accepted,context)
|
|
validate_premises(fixed,accepted,context)
|
|
from viano_bug_analysis.analysis.hypothesis_contracts import build_context,validate_proposals
|
|
from viano_bug_analysis.analysis.hypothesis_config import HypothesisLimits
|
|
accepted_doc=json.loads((root/'case_interpretation.json').read_text(encoding='utf-8'))
|
|
hc=build_context(context,accepted_doc,'accepted.json',context['joint_generation']['policy'],[],HypothesisLimits())
|
|
mapping={v['interpretation_pointer']:k for k,v in hc['interpretation_sources'].items()}
|
|
canonical=copy.deepcopy(fixed)
|
|
for proposal in canonical['hypotheses']:
|
|
proposal.pop('premises')
|
|
proposal['interpretation_ids']=[mapping[p] for p in proposal.pop('interpretation_paths')]
|
|
validate_proposals(canonical,hc)
|
|
assert raw==before
|
|
for old,new in zip(raw['hypotheses'],fixed['hypotheses']):
|
|
assert {k:v for k,v in old.items() if k not in {'source_ids','interpretation_paths'}}=={k:v for k,v in new.items() if k not in {'source_ids','interpretation_paths'}}
|
|
assert set(old['interpretation_paths'])<=set(new['interpretation_paths'])
|
|
del context['joint_generation']['recovery_version']
|
|
assert complete_dependencies(raw,accepted,context)==raw
|
|
|
|
|
|
def test_invalid_premise_path_is_not_promoted(built_case):
|
|
def bad(d):d['hypotheses']['hypotheses'][0]['premises'][0]['interpretation_paths']=['/interpretation/unknowns/9999']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).code=='HYPOTHESIS_PREMISE_SOURCE'
|
|
|
|
|
|
def test_malformed_candidate_does_not_discard_siblings(built_case):
|
|
def bad(d):d['hypotheses']['hypotheses'][1].pop('mechanism')
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
result=hypotheses(store,gw);assert result.code=='HYPOTHESIS_SCHEMA'
|
|
with PackageReader(result.package) as r:
|
|
context=json.loads(r.read_bytes(r.manifest['artifacts']['hypothesis_context']))
|
|
assert set(context['joint_retained']['proposals'])=={'0','2'}
|
|
assert hypotheses(store,gw).status=='COMPLETED'
|
|
|
|
|
|
def test_added_dependency_prevents_false_independence_after_patch(built_case):
|
|
def bad(d):
|
|
d['interpretation']['observed_behavior'][0]['source_ids']=['F0001']
|
|
# Explicit premise points at changed statement, missing only from parent list.
|
|
d['hypotheses']['hypotheses'][1]['premises'][0]['interpretation_paths']=['/interpretation/observed_behavior/0']
|
|
store=built_case[0];adapter=Adapter(bad);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='REJECTED'
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
assert hypotheses(store,gw).code=='HYPOTHESIS_DEPENDENCY_CHANGED'
|
|
|
|
|
|
def nested_corpus():
|
|
from pathlib import Path
|
|
root=Path('tests/fixtures/llm_rejections/20261020/joint-nested-envelope')
|
|
context=json.loads((root/'context.json').read_text(encoding='utf-8'))
|
|
context['joint_generation']['recovery_version']=2
|
|
return root,context
|
|
|
|
|
|
def test_nested_joint_blocks_are_recovered_and_replay_without_extra_calls(built_case,tmp_path):
|
|
def nested(d):d['hypotheses']['interpretation']=d.pop('interpretation')
|
|
store=built_case[0];adapter=Adapter(nested);gw=gateway(adapter)
|
|
assert interpret(store,gw).status=='COMPLETED'
|
|
final=hypotheses(store,gw);assert final.status=='COMPLETED'
|
|
from viano_bug_analysis.lifecycle import Workspace,CheckpointStore
|
|
recipient=CheckpointStore(Workspace(tmp_path/'recipient'))
|
|
recipient.intake(final.package)
|
|
assert interpret(recipient,gw).reused
|
|
assert hypotheses(recipient,gw).reused
|
|
assert len(adapter.requests)==1
|
|
|
|
|
|
def test_real_nested_reply_passes_full_interpretation_and_hypothesis_validation():
|
|
import hashlib
|
|
from viano_bug_analysis.analysis.joint_v2 import joint_document,complete_dependencies,validate_premises
|
|
from viano_bug_analysis.analysis.contracts import validate_interpretation
|
|
from viano_bug_analysis.analysis.hypothesis_contracts import build_context,validate_proposals
|
|
from viano_bug_analysis.analysis.hypothesis_config import HypothesisLimits
|
|
root,context=nested_corpus()
|
|
for name,digest in json.loads((root/'hashes.json').read_text(encoding='utf-8')).items():
|
|
assert hashlib.sha256((root/name).read_bytes()).hexdigest()==digest
|
|
raw=json.loads((root/'case_plan_v2.json').read_text(encoding='utf-8'))['outcome']['document']
|
|
before=copy.deepcopy(raw)
|
|
doc=joint_document(raw,context)
|
|
assert doc['interpretation']==raw['hypotheses']['interpretation']
|
|
assert doc['hypotheses']['hypotheses']==raw['hypotheses']['hypotheses']
|
|
validate_interpretation(doc['interpretation'],context)
|
|
proposals=complete_dependencies(doc['hypotheses'],doc['interpretation'],context)
|
|
validate_premises(proposals,doc['interpretation'],context)
|
|
hc=build_context(context,{'interpretation':doc['interpretation']},'accepted.json',context['joint_generation']['policy'],[],HypothesisLimits())
|
|
mapping={v['interpretation_pointer']:k for k,v in hc['interpretation_sources'].items()}
|
|
for proposal in proposals['hypotheses']:
|
|
proposal.pop('premises')
|
|
proposal['interpretation_ids']=[mapping[p] for p in proposal.pop('interpretation_paths')]
|
|
validate_proposals(proposals,hc)
|
|
assert raw==before
|
|
context['joint_generation']['recovery_version']=1
|
|
assert joint_document(raw,context)==raw
|
|
|
|
|
|
@pytest.mark.parametrize('extra',[None,{}, {'observed_behavior':[]}])
|
|
def test_conflicting_nested_interpretations_are_never_selected(extra):
|
|
from viano_bug_analysis.analysis.joint_v2 import joint_document
|
|
from viano_bug_analysis.analysis.errors import InterpretationError
|
|
_,context=nested_corpus()
|
|
raw={'interpretation':extra,'hypotheses':{'interpretation':{},'hypotheses':[]}}
|
|
with pytest.raises(InterpretationError,match='INTERPRETATION_SCHEMA'):joint_document(raw,context)
|
|
|
|
|
|
def test_wrapper_repair_does_not_drop_unknown_siblings():
|
|
from viano_bug_analysis.analysis.joint_v2 import joint_document
|
|
_,context=nested_corpus()
|
|
raw={'hypotheses':{'interpretation':{},'hypotheses':[],'surprise':'unrecognized'}}
|
|
assert joint_document(raw,context)==raw
|
|
|
|
|
|
def test_real_patch_preserves_only_unchanged_identity_citations():
|
|
from viano_bug_analysis.analysis.joint_v2 import preserve_entity_citations
|
|
from viano_bug_analysis.analysis.contracts import validate_interpretation
|
|
from viano_bug_analysis.analysis.errors import InterpretationError
|
|
root,context=nested_corpus()
|
|
old=json.loads((root/'case_plan_interpretation_repair_v1.json').read_text(encoding='utf-8'))['outcome']['document']
|
|
patch=json.loads((root/'case_plan_statement_repair_v2.json').read_text(encoding='utf-8'))['outcome']['document']
|
|
before=copy.deepcopy(patch)
|
|
fixed=copy.deepcopy(old)
|
|
for p in patch['patches']:
|
|
if p['path']=='/unknowns':fixed['unknowns']=p['replacement']
|
|
else:fixed['relevant_entities'][4]=preserve_entity_citations(old['relevant_entities'][4],p['replacement'],context)
|
|
validate_interpretation(fixed,context)
|
|
assert 'F0029' in fixed['relevant_entities'][4]['source_ids']
|
|
assert patch==before
|
|
replacement=patch['patches'][0]['replacement']
|
|
for field,value in [('reason','Different technical claim'),('entity_id','N0001')]:
|
|
changed={**replacement,field:value}
|
|
assert preserve_entity_citations(old['relevant_entities'][4],changed,context)==changed
|
|
unknown=copy.deepcopy(replacement);unknown['source_ids'].append('F9999')
|
|
fixed['relevant_entities'][4]=preserve_entity_citations(old['relevant_entities'][4],unknown,context)
|
|
with pytest.raises(InterpretationError,match='INTERPRETATION_SOURCE_UNKNOWN'):validate_interpretation(fixed,context)
|
|
|
|
|
|
def test_empty_candidate_repair_request_does_not_request_hypothesis_review(monkeypatch):
|
|
from viano_bug_analysis.analysis import joint_v2
|
|
from viano_bug_analysis.analysis.json_consolidation import restore
|
|
root,context=nested_corpus()
|
|
source=json.loads((root/'case_plan_interpretation_repair_v1.json').read_text(encoding='utf-8'))
|
|
monkeypatch.setattr(joint_v2,'read_json',lambda *a,**kw:context)
|
|
request=joint_v2.repair_request({'privacy':'configured'},None,('error',{'context_ref':'context'},source),{})
|
|
wire=json.loads(request['messages'][1]['content']);body=restore(wire['repair'])
|
|
assert body['hypotheses'] is None
|
|
assert body['review_contract']=={'type':'array','maxItems':0}
|
|
assert 'F0029' in body['preserve_identity_citations']['/relevant_entities/4']
|
|
assert 'Return reviews=[]' in request['messages'][0]['content']
|
|
|
|
|
|
def test_real_scoped_patch_projection_preserves_existing_provenance(monkeypatch):
|
|
from viano_bug_analysis.analysis import joint_v2
|
|
from viano_bug_analysis.analysis.contracts import validate_interpretation
|
|
root,context=nested_corpus()
|
|
source=json.loads((root/'case_plan_interpretation_repair_v1.json').read_text(encoding='utf-8'))
|
|
call=json.loads((root/'case_plan_statement_repair_v2.json').read_text(encoding='utf-8'))
|
|
original=source['outcome']['document']
|
|
body={'issues':joint_v2.issues(original,context)}
|
|
monkeypatch.setattr(joint_v2,'parent',lambda *a,**kw:(source,body))
|
|
projected=joint_v2.project(None,call,context)
|
|
validate_interpretation(projected,context)
|
|
assert projected['relevant_entities'][4]['reason']==original['relevant_entities'][4]['reason']
|
|
assert 'F0029' in projected['relevant_entities'][4]['source_ids']
|