Prioritize investigation hypotheses by confidence after bootstrap
This commit is contained in:
1 parent
f4082585ae
commit
c44ccd5ce3
5 files changed
+128
-2
No files matched your search
@@ -0,0 +1,35 @@
|
|||||||
|
# Confidence-prioritized investigation scheduling
|
||||||
|
|
||||||
|
The controller gives every active, eligible hypothesis one bootstrap turn in
|
||||||
|
numeric ID order. After that it chooses by qualitative confidence:
|
||||||
|
strongly_supported > supported > plausible > unassessed > weak > weakened.
|
||||||
|
Equal levels use numeric hypothesis ID (H1 before H2 before H10), not rotation.
|
||||||
|
These ranks are ordering values, not probabilities or a new model-owned budget.
|
||||||
|
|
||||||
|
Selection is recomputed between turns. A strengthening hypothesis can continue;
|
||||||
|
a weakened hypothesis yields to a higher-ranked alternative without losing its
|
||||||
|
remaining budget or evidence. Initial turns generally issue queries: their results
|
||||||
|
are semantically assessed on a subsequent turn, so the bootstrap does not claim
|
||||||
|
that all hypotheses are already evaluated after three calls.
|
||||||
|
|
||||||
|
Existing eligibility checks, positive action costs, rewards, iteration safeguards,
|
||||||
|
terminal dispositions and final pending-result assessments remain in force. An
|
||||||
|
interrupted/pending active turn resumes as before; it is not preempted or rewritten.
|
||||||
|
Existing cases adopt the scheduler at their next selection boundary. Archived
|
||||||
|
states, contexts and model request hashes are not changed. The historical cursor
|
||||||
|
field is retained for file compatibility but no longer determines selection.
|
||||||
|
|
||||||
|
## Completion remains separate from selection
|
||||||
|
|
||||||
|
The model may finish its selected hypothesis with remaining credit, or reject it.
|
||||||
|
Supported confidence requires stored evidence. This does not itself globally
|
||||||
|
finish the case. The controller transitions to BUILDING_DIAGNOSIS after all
|
||||||
|
hypotheses have terminal accounts and pending results have been handled, whether
|
||||||
|
through model finish/reject, budget exhaustion or controller limits. No rule
|
||||||
|
requires spending every allocated unit. A weak hypothesis is deprioritized, not
|
||||||
|
automatically rejected just because another ranks higher.
|
||||||
|
|
||||||
|
The final diagnosis evaluates all alternatives, root-cause evidence and adequacy
|
||||||
|
of a countercheck. Only an accepted supported diagnosis can proceed to a coding
|
||||||
|
change request; inconclusive cases go to review. An automatic global early finish
|
||||||
|
on a single high-confidence declaration is not introduced by this scheduler.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Deterministic scheduling, independent of model calls and budget accounting."""
|
||||||
|
|
||||||
|
CONFIDENCE_ORDER = {
|
||||||
|
'strongly_supported': 5,
|
||||||
|
'supported': 4,
|
||||||
|
'plausible': 3,
|
||||||
|
'unassessed': 2,
|
||||||
|
'weak': 1,
|
||||||
|
'weakened': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def priority_order(hypotheses):
|
||||||
|
"""One bootstrap turn each, then confidence with numeric ID tie-breaking.
|
||||||
|
|
||||||
|
Eligibility, pending-action checks and iteration/budget limits remain the
|
||||||
|
controller's responsibility. An interrupted active turn is never rescheduled.
|
||||||
|
These ranks order qualitative levels; they are not confidence probabilities.
|
||||||
|
"""
|
||||||
|
ids = sorted(hypotheses, key=lambda hid:int(hid[1:]))
|
||||||
|
positions = {hid:index for index,hid in enumerate(ids)}
|
||||||
|
def rank(hid):
|
||||||
|
hypothesis = hypotheses[hid]
|
||||||
|
return (0 if hypothesis['iterations']==0 else 1,
|
||||||
|
0 if hypothesis['iterations']==0 else -CONFIDENCE_ORDER[hypothesis['confidence']],
|
||||||
|
int(hid[1:]))
|
||||||
|
return [(positions[hid],hid) for hid in sorted(ids,key=rank)]
|
||||||
@@ -239,8 +239,9 @@ class Investigator:
|
|||||||
snapshot=budget.snapshot(case_id,package_id)
|
snapshot=budget.snapshot(case_id,package_id)
|
||||||
ids=sorted(state["hypotheses"],key=lambda value:int(value[1:]))
|
ids=sorted(state["hypotheses"],key=lambda value:int(value[1:]))
|
||||||
selected=None
|
selected=None
|
||||||
for offset in range(len(ids)):
|
from .investigation_scheduler import priority_order
|
||||||
pos=(state["cursor"]+offset)%len(ids);hid=ids[pos];h=state["hypotheses"][hid];account=snapshot.state["accounts"][hid]
|
for pos,hid in priority_order(state['hypotheses']):
|
||||||
|
h=state["hypotheses"][hid];account=snapshot.state["accounts"][hid]
|
||||||
if h["disposition"]!="active":continue
|
if h["disposition"]!="active":continue
|
||||||
if any(a["status"]!="settled" for a in account["actions"].values()) and h["last_record_ref"] is None:
|
if any(a["status"]!="settled" for a in account["actions"].values()) and h["last_record_ref"] is None:
|
||||||
raise InvestigationError("INVESTIGATION_UNOWNED_PENDING_ACTION")
|
raise InvestigationError("INVESTIGATION_UNOWNED_PENDING_ACTION")
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from viano_bug_analysis.analysis.investigation_scheduler import priority_order
|
||||||
|
|
||||||
|
|
||||||
|
def hypothesis(confidence='unassessed',iterations=1):
|
||||||
|
return {'confidence':confidence,'iterations':iterations}
|
||||||
|
|
||||||
|
|
||||||
|
def order(hypotheses):
|
||||||
|
return [hid for _,hid in priority_order(hypotheses)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_bootstrap_turns_precede_already_strong_hypotheses():
|
||||||
|
states={'H1':hypothesis('strongly_supported'),'H2':hypothesis(iterations=0),'H3':hypothesis(iterations=0)}
|
||||||
|
assert order(states)==['H2','H3','H1']
|
||||||
|
states['H2']['iterations']=1
|
||||||
|
assert order(states)==['H3','H1','H2']
|
||||||
|
|
||||||
|
|
||||||
|
def test_confidence_rises_and_falls_without_changing_budget_or_state():
|
||||||
|
states={'H1':hypothesis('plausible'),'H2':hypothesis('supported'),'H3':hypothesis('weak')}
|
||||||
|
before=copy.deepcopy(states)
|
||||||
|
assert order(states)==['H2','H1','H3']
|
||||||
|
assert states==before
|
||||||
|
states['H2']['confidence']='weakened'
|
||||||
|
assert order(states)==['H1','H3','H2']
|
||||||
|
states['H3']['confidence']='strongly_supported'
|
||||||
|
assert order(states)==['H3','H1','H2']
|
||||||
|
|
||||||
|
|
||||||
|
def test_equal_confidence_uses_numeric_hypothesis_id_not_rotation_or_string_sort():
|
||||||
|
states={hid:hypothesis('supported') for hid in ['H10','H5','H3','H2','H1']}
|
||||||
|
assert order(states)==['H1','H2','H3','H5','H10']
|
||||||
|
assert dict(priority_order(states))[0]=='H1'
|
||||||
|
|
||||||
|
|
||||||
|
def test_unassessed_alternative_is_preferred_over_weakened_candidate():
|
||||||
|
assert order({'H1':hypothesis('weak'),'H2':hypothesis(),'H3':hypothesis('weakened')})==['H2','H1','H3']
|
||||||
@@ -270,3 +270,27 @@ def test_provider_configuration_error_blocks_without_abandoning_hypotheses(tool_
|
|||||||
@pytest.mark.parametrize("name",["investigation-output","investigation-state","investigation-context"])
|
@pytest.mark.parametrize("name",["investigation-output","investigation-state","investigation-context"])
|
||||||
def test_loop_schemas_valid(name):
|
def test_loop_schemas_valid(name):
|
||||||
Draft202012Validator.check_schema(json.loads(files("viano_bug_analysis.schemas").joinpath(name+'-v1.schema.json').read_text()))
|
Draft202012Validator.check_schema(json.loads(files("viano_bug_analysis.schemas").joinpath(name+'-v1.schema.json').read_text()))
|
||||||
|
|
||||||
|
|
||||||
|
def test_confidence_scheduler_keeps_growing_candidate_then_switches_after_weakening(tool_case):
|
||||||
|
def change(reply,context):
|
||||||
|
hid=context['selected_hypothesis']
|
||||||
|
turns=sum(c['selected_hypothesis']==hid for c in adapter.contexts)
|
||||||
|
if hid=='H1' and turns in (2,3):
|
||||||
|
reply['update']['confidence']='supported' if turns==2 else 'weakened'
|
||||||
|
reply['next']={'kind':'tool','operation':'source.read_context',
|
||||||
|
'arguments':{'source_ref':'tool/code.cpp','start_line':1,'end_line':2}}
|
||||||
|
adapter=LoopAdapter(change)
|
||||||
|
loop=Investigator(tool_case[0],gateway(adapter))
|
||||||
|
first=run(loop,steps=4)
|
||||||
|
assert first.status=='YIELDED'
|
||||||
|
assert [c['selected_hypothesis'] for c in adapter.contexts]==['H1','H2','H3','H1']
|
||||||
|
# Resume the persisted confidence state; neither cursor nor a restart forces rotation.
|
||||||
|
result=run(Investigator(tool_case[0],gateway(adapter)),steps=2)
|
||||||
|
assert [c['selected_hypothesis'] for c in adapter.contexts]==['H1','H2','H3','H1','H1','H2']
|
||||||
|
saved=state(result)
|
||||||
|
assert saved['hypotheses']['H1']['confidence']=='weakened'
|
||||||
|
assert saved['hypotheses']['H1']['disposition']=='active'
|
||||||
|
account=BudgetManager(tool_case[0]).snapshot(*CASE).state['accounts']['H1']
|
||||||
|
assert account['remaining']>0 and account['status']=='investigating'
|
||||||
|
assert PackageValidator().validate(result.package).valid
|
||||||
Reference in new issue
Block a user