Constrain nested bootstrap findings with versioned native output shapes

This commit is contained in:
Nico committed 2026-10-18 23:55:16 +02:00
1 parent 67843454e4
commit f5a87dfca3
14 files changed
+173 -12

No files matched your search

@@ -24,6 +24,7 @@ class InvestigationConfig:
lean_checkpoint_steps: int = 4
lean_protocol: int = 1
bootstrap_json_framing: bool = False
bootstrap_framing_version: int = 2
def __post_init__(self):
if any(type(value) is not int or value <= 0 for name,value in asdict(self).items() if name not in {'lean_loop','lean_checkpoint_steps','bootstrap_json_framing'}):
@@ -33,6 +34,8 @@ class InvestigationConfig:
raise InvestigationError('INVESTIGATION_CONFIG')
if type(self.lean_loop) is not bool or type(self.bootstrap_json_framing) is not bool:
raise InvestigationError('INVESTIGATION_CONFIG')
if self.bootstrap_framing_version not in {1,2}:
raise InvestigationError('INVESTIGATION_CONFIG')
@classmethod
def load(cls, path):
@@ -150,7 +150,8 @@ def _build_prompt(context):
from .investigation_plan import framed_view
visible = framed_view(context,visible)
task = 'advance_investigation_goal'
return {"prompt_version":"investigation_bootstrap_v21" if context.get("bootstrap_json_framing") else version,"privacy":"configured","output_schema":schema,
bootstrap_version='investigation_bootstrap_v22' if context.get('bootstrap_framing_version')==2 else 'investigation_bootstrap_v21'
return {"prompt_version":bootstrap_version if context.get("bootstrap_json_framing") else version,"privacy":"configured","output_schema":schema,
"messages":[{"role":"system","content":files("viano_bug_analysis.llm.prompts").joinpath(prompt_file+".txt").read_text(encoding="utf-8")},
{"role":"user","content":encode({"task":task,"input":visible,"output_schema":schema})}]}
@@ -291,7 +291,10 @@ class Investigator:
if state['policy'].get('lean_protocol',1)==1 or state['hypotheses'][hid]['iterations']>0:
context['lean_session']=copy.deepcopy(state['hypotheses'][hid].get('lean_session',{'turns':[],'checkpoint':False}))
if state['policy'].get('lean_protocol',1)==2:context['lean_protocol']=2
elif state['policy'].get('bootstrap_json_framing'):context['bootstrap_json_framing']=True
elif state['policy'].get('bootstrap_json_framing'):
context['bootstrap_json_framing']=True
if state['policy'].get('bootstrap_framing_version',1)==2:
context['bootstrap_framing_version']=2
if len(json_bytes(context))>self.limits.max_context_bytes:raise InvestigationError("INVESTIGATION_CONTEXT_LIMIT")
validate(context,"investigation-context")
return context
@@ -318,7 +321,10 @@ class Investigator:
if state is None:
with PackageReader(self._latest(case_id,package_id),self.store.limits) as reader:state=self._begin(reader)
state,_=self._save(state)
if self.config_explicit and {**asdict(InvestigationConfig()),**state["policy"]}!=asdict(self.config):raise InvestigationError("INVESTIGATION_POLICY_CHANGED")
effective_policy={**asdict(InvestigationConfig()),**state['policy']}
if state['policy'].get('bootstrap_json_framing') and 'bootstrap_framing_version' not in state['policy']:
effective_policy['bootstrap_framing_version']=1
if self.config_explicit and effective_policy!=asdict(self.config):raise InvestigationError("INVESTIGATION_POLICY_CHANGED")
if state["active"] and state["active"]["phase"]=="executing":return self._execute_plan(state,budget)
if state["active"] is None:
state.setdefault('early_stop_protocol',1)
@@ -28,7 +28,7 @@ def measure_package(path):
contexts[hashlib.sha256(json_bytes(original['messages'])).hexdigest()] = context
elif name.startswith('llm/operations/') and name.endswith('/request.json'):
req = json.loads(reader.read_bytes(name))
if req.get('prompt_version','').startswith('investigation_project_'):
if req.get('prompt_version','').startswith(('investigation_project_','investigation_bootstrap_')):
requests.append(req)
samples, unmatched = [], 0
for req in requests:
+1 -1
View File
@@ -155,7 +155,7 @@ class LLMGateway:
LOGGER.warning("LLM endpoint unavailable provider=%s code=%s", provider.id, code)
except ValidationError as invalid:
info={'validation_keyword':invalid.validator}
if request['prompt_version'] in {'investigation_bootstrap_v21','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}:
if request['prompt_version'] in {'investigation_bootstrap_v22','investigation_bootstrap_v21','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}:
from .diagnostics import schema_feedback
info.update(schema_feedback(invalid,include_keyword=True))
error = LLMError("LLM_OUTPUT_SCHEMA",diagnostics=info)
+20 -1
View File
@@ -21,6 +21,23 @@ from .provider_base import ProviderResponse
from .diagnostics import count, sanitize, parse_response_object
def bootstrap_shape(schema):
"""Nested syntax contract only; full controller validation remains authoritative."""
def shape(rule):
result={k:rule[k] for k in ('type','required','additionalProperties') if k in rule}
if 'properties' in rule:result['properties']={k:shape(v) for k,v in rule['properties'].items()}
if 'items' in rule:result['items']=shape(rule['items'])
if 'anyOf' in rule:result['anyOf']=[shape(v) for v in rule['anyOf']]
if 'const' in rule:
from .diagnostics import json_type
result['type']=json_type(rule['const'])
return result
return {'type':'object','additionalProperties':False,'required':['assessment','update','next'],
'properties':{'assessment':shape(schema['properties']['assessment']),
'update':shape(schema['properties']['update']),
'next':{'type':'object','additionalProperties':True}}}
class GeminiProvider:
def __init__(self):
self._cache = {}
@@ -132,6 +149,8 @@ class GeminiProvider:
'update':{'type':'object','additionalProperties':True},
'next':{'type':'object','additionalProperties':True}},
'required':['assessment','update','next'],'additionalProperties':False}
if request['prompt_version']=='investigation_bootstrap_v22' and config.response_format=='json_object':
generation['responseJsonSchema']=bootstrap_shape(request['output_schema'])
full = {'contents': prefix + contents, 'generationConfig': generation}
if system['parts']:
full['systemInstruction'] = system
@@ -142,7 +161,7 @@ class GeminiProvider:
if config.use_context_cache:
status = 'below_minimum'
if (len(json_bytes([system, prefix]).decode('utf-8')) >= config.cache_min_prefix_chars
or request['prompt_version']=='investigation_bootstrap_v21' or request['prompt_version'] in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}):
or request['prompt_version'] in {'investigation_bootstrap_v21','investigation_bootstrap_v22'} or request['prompt_version'] in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}):
with self._lock:
now = time.monotonic()
self._cache = {k: v for k, v in self._cache.items() if v[1] > now}
+1 -1
View File
@@ -10,7 +10,7 @@ from viano_bug_analysis.lifecycle.storage import json_bytes
VERSION = 'stable_case_v1'
SUPPORTED_PROMPTS = {f'investigation_project_v{n}' for n in (4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)}
SUPPORTED_PROMPTS.add('investigation_bootstrap_v21')
SUPPORTED_PROMPTS.update({'investigation_bootstrap_v21','investigation_bootstrap_v22'})
STABLE_FIELDS = frozenset({'format', 'format_version', 'prompt_version', 'case',
'sources', 'interpretation', 'budget_policy', 'policy', 'tool_schema',
'unread_sources', 'unread_inventory_scope'})
@@ -110,6 +110,13 @@
},
"bootstrap_json_framing": {
"type": "boolean"
},
"bootstrap_framing_version": {
"type": "integer",
"enum": [
1,
2
]
}
}
},
@@ -802,6 +809,9 @@
},
"tool_guidance_version": {
"const": 1
},
"bootstrap_framing_version": {
"const": 2
}
}
}
@@ -106,6 +106,13 @@
},
"bootstrap_json_framing": {
"type": "boolean"
},
"bootstrap_framing_version": {
"type": "integer",
"enum": [
1,
2
]
}
}
},