Files
VianoBugAnalysis/src/viano_bug_analysis/analysis/joint_planning.py
T

146 lines
7.9 KiB
Python

"""One paid reasoning pass; two independently accepted phase artifacts."""
import copy
import hashlib
from importlib.resources import files
from viano_bug_analysis.case.contracts import read_json
from viano_bug_analysis.lifecycle.storage import json_bytes
from .errors import InterpretationError
from .hypothesis_config import HypothesisError
VERSION = "case_plan_v1"
REPAIR = "case_plan_interpretation_repair_v1"
def digest(value):
return hashlib.sha256(json_bytes(value)).hexdigest()
def schemas(context):
from .contracts import output_schema
from .hypothesis_contracts import output_schema as hypothesis_schema
interpretation = output_schema(context)
hypotheses = hypothesis_schema(context["joint_generation"]["policy"])
proposal = hypotheses["$defs"]["proposal"]
proposal["properties"].pop("interpretation_ids")
proposal["properties"]["interpretation_paths"] = {
"type": "array", "minItems": 1, "uniqueItems": True,
"items": {"type": "string", "pattern": "^/interpretation/[a-z_]+(/[0-9]+)?$"}}
proposal["required"] = ["interpretation_paths" if f == "interpretation_ids" else f for f in proposal["required"]]
return interpretation, hypotheses
def build_request(context):
from .phase_selection import select_context
from .model_context import compact_context, model_json
from .json_consolidation import consolidate
view = select_context(context, "interpretation")
settings = view.pop("joint_generation")
interpretation, hypotheses = schemas(context)
# Full child contracts occur ONCE in prompt. Permissive transport fields allow
# one malformed semantic block to survive for separate local validation.
return {"prompt_version": VERSION, "privacy": "configured",
"output_schema": {"type": "object", "properties": {"interpretation": {}, "hypotheses": {}},
"additionalProperties": False},
"messages": [{"role": "system", "content": files("viano_bug_analysis.llm.prompts").joinpath(VERSION + ".txt").read_text(encoding="utf-8")},
{"role": "user", "content": model_json({"task": "interpret_and_propose", "input": consolidate(compact_context(view)),
"policy": settings["policy"], "prior_generations": settings["prior_generations"],
"block_contracts": {"interpretation": interpretation, "hypotheses": hypotheses}})}]}
def normalize_block(payload, schema):
from viano_bug_analysis.llm.answer_recovery import normalize_schema, parse_complete, CAPTURE_LIMIT
if isinstance(payload, str) and len(payload.encode("utf-8")) <= CAPTURE_LIMIT:
decoded, _ = parse_complete(payload)
if decoded is not None:
payload = decoded
return normalize_schema(payload, schema)[0]
def interpretation_payload(call, context):
from .contracts import output_schema
payload = call["outcome"].get("document")
if call["outcome"].get("metadata", {}).get("prompt_version") == VERSION:
payload = payload.get("interpretation") if isinstance(payload, dict) else None
payload = normalize_block(payload, output_schema(context))
return payload
def hypothesis_payload(reader, call, context):
"""Bind the candidate to the exact accepted interpretation, never a repaired one."""
accepted = read_json(reader, context["interpretation_ref"], "case-interpretation", 2 * 1024**2)
case_context = context["case_context"]
if (call["result_ref"] != accepted["llm_result_ref"] or call["status"] != "SUCCESS"
or call["phase"] != "INTERPRETING_CASE"
or any(call[f] != accepted[f] for f in ("case_id", "package_id", "analysis_revision"))
or call["outcome"].get("metadata", {}).get("prompt_version") != VERSION
or interpretation_payload(call, case_context) != accepted["interpretation"]
or accepted["llm_metadata"] != call["outcome"].get("metadata")
or accepted["prompt_version"] != VERSION
or accepted["case_ref"] != case_context["case_ref"]
or accepted["case_sha256"] != case_context["case_sha256"]
or digest(accepted) != context["interpretation_sha256"]
or case_context["joint_generation"]["policy"] != context["policy"]
or case_context["joint_generation"]["prior_generations"] != context["prior_generations"]):
raise HypothesisError("HYPOTHESIS_LLM_BINDING")
stored_context = read_json(reader, accepted["context_ref"], "case-interpretation-context", 2 * 1024**2)
if stored_context != case_context or accepted["context_sha256"] != digest(case_context):
raise HypothesisError("HYPOTHESIS_LLM_BINDING")
from .contracts import validate_interpretation
validate_interpretation(accepted["interpretation"], case_context)
request = read_request(reader, call)
expected = build_request(case_context)
from .correction import replay_request
expected = replay_request(reader, expected, call, "interpretation")
expected["privacy"] = request.get("privacy")
if request != expected:
raise HypothesisError("HYPOTHESIS_LLM_BINDING")
raw = call["outcome"]["document"].get("hypotheses")
_, schema = schemas(case_context)
raw = normalize_block(raw, schema)
from jsonschema import Draft202012Validator
if not Draft202012Validator(schema).is_valid(raw):
raise HypothesisError("HYPOTHESIS_SCHEMA")
result = copy.deepcopy(raw)
pointers = {v["interpretation_pointer"]: k for k,v in context["interpretation_sources"].items()}
for proposal in result["hypotheses"]:
paths = proposal.pop("interpretation_paths")
if any(p not in pointers for p in paths):
raise HypothesisError("HYPOTHESIS_SOURCE_UNKNOWN")
proposal["interpretation_ids"] = [pointers[p] for p in paths]
return result
def read_request(reader, call):
from viano_bug_analysis.package.manifest import parse_manifest
from viano_bug_analysis.case.contracts import require_file
require_file(reader, call["request_ref"])
with reader._archive.open(call["request_ref"]) as stream:
raw = reader.validator._read_bounded(stream, min(4 * 1024**2, reader.validator.limits.max_file_bytes))
if hashlib.sha256(raw).hexdigest() != call["input_request_hash"]:
raise InterpretationError("INTERPRETATION_LLM_BINDING")
return parse_manifest(raw)
def joint_call(reader, context):
accepted = read_json(reader, context["interpretation_ref"], "case-interpretation", 2 * 1024**2)
call = read_json(reader, accepted["llm_result_ref"], "llm-call", 4 * 1024**2)
return call if call["outcome"].get("metadata", {}).get("prompt_version") == VERSION else None
def repair_interpretation(request, reader, failure, feedback):
"""Replace only interpretation; hypotheses from the rejected parent are invalidated."""
from .contracts import output_schema
context = read_json(reader, failure[1]["context_ref"], "case-interpretation-context", 2 * 1024**2)
from .phase_selection import select_context
from .model_context import compact_context, model_json
from .json_consolidation import consolidate
view = select_context(context, "interpretation")
view.pop("joint_generation", None)
encoding = files("viano_bug_analysis.llm.prompts").joinpath(VERSION + ".txt").read_text(encoding="utf-8").split("\nENCODING\n", 1)[1]
return {"prompt_version": REPAIR, "privacy": request["privacy"], "output_schema": output_schema(context),
"messages": [{"role": "system", "content": files("viano_bug_analysis.llm.prompts").joinpath(REPAIR + ".txt").read_text(encoding="utf-8") + "\n" + encoding},
{"role": "user", "content": model_json({"task": "replace_interpretation_only",
"input": consolidate(compact_context(view)), "rejected_interpretation": interpretation_payload(failure[2],context),
"validation_feedback": feedback, "output_schema": output_schema(context)})}]}