"""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)})}]}