Recover misplaced joint blocks and preserve unchanged entity citations

This commit is contained in:
Nico committed 2026-10-20 09:50:56 +02:00
1 parent c3a4690a55
commit 3872735c0a
14 files changed
+265 -10

No files matched your search

+30
View File
@@ -122,3 +122,33 @@ verification is not silently filled: the scoped statement correction must provid
The original 2026-10-20 replies and a bounded validation context are preserved in the
curated rejection corpus. Offline regression checks hashes, reference union, sibling
preservation and rejection of invented references. No paid API request was needed.
## Structural recovery revision 2
New contexts freeze `recovery_version = 2`; revision 1 and unmarked historical
contexts keep their original request/projection behavior. This revision includes
revision 1 dependency completion and sibling preservation.
One exact misplaced wrapper is recoverable: a sole top-level `hypotheses` object
containing exactly an `interpretation` object and a `hypotheses` array. These are
copied to the two intended blocks, without changing or dropping any content.
Competing top-level interpretations (even null), extra sibling keys and malformed
wrappers are never guessed away. Both recovered blocks still undergo all existing
schema, source, dependency and semantic contract checks. The original call stays
unchanged and remains the usage/provenance source during replay and import.
During a scoped entity correction, existing eligible identity/mention citations
are restored only when BOTH entity_id and reason are unchanged. Eligible references
come from the original statement and the frozen entity's matching capture/mention
provenance. New evidence is never invented. Changed claims receive no restoration;
unknown replacement sources remain present and invalid. Requests explicitly explain
which existing citations to preserve. When no reusable hypotheses exist, the request
explicitly requires an empty reviews array instead of asking to reconstruct them.
The curated `joint-nested-envelope` corpus preserves the actual rejected replies,
their hashes and bounded context. Offline tests confirm that the initial reply passes
full interpretation and hypothesis validation after lossless wrapper recovery and
existing dependency normalization. A separate regression confirms the later scoped
patch passes interpretation validation after preserving unchanged identity evidence.
No paid API request was executed to establish these results.
@@ -32,5 +32,5 @@ class HypothesisPromptBuilder(PromptBuilder):
from .json_consolidation import consolidate
model_context = consolidate(model_context)
return {"prompt_version": version, "privacy": "configured", "output_schema": schema,
"messages": [{"role": "system", "content": files("viano_bug_analysis.llm.prompts").joinpath(version + ".txt").read_text(encoding="utf-8") + ("\nPreserve joint_retained.proposals exactly at their zero-based positions. Generate ONLY the other positions; return the full array for deterministic merging/validation. Retained proposals were explicitly reviewed; do not rewrite or reorder them." if context.get("joint_retained") else "") + ("\nRepair only invalid hypotheses at unlocked positions. Preserve their valid claims and mechanisms; correct the reported dependency/source defect. Do not regenerate accepted interpretation or locked candidates." if context.get("joint_retained") and context.get("case_context",{}).get("joint_generation",{}).get("recovery_version")==1 else "")},
"messages": [{"role": "system", "content": files("viano_bug_analysis.llm.prompts").joinpath(version + ".txt").read_text(encoding="utf-8") + ("\nPreserve joint_retained.proposals exactly at their zero-based positions. Generate ONLY the other positions; return the full array for deterministic merging/validation. Retained proposals were explicitly reviewed; do not rewrite or reorder them." if context.get("joint_retained") else "") + ("\nRepair only invalid hypotheses at unlocked positions. Preserve their valid claims and mechanisms; correct the reported dependency/source defect. Do not regenerate accepted interpretation or locked candidates." if context.get("joint_retained") and context.get("case_context",{}).get("joint_generation",{}).get("recovery_version") in (1,2) else "")},
{"role": "user", "content": encode({"task": "generate_hypotheses", "input": model_context, "output_schema": schema})}]}
@@ -134,7 +134,7 @@ class CaseInterpreter:
from .hypothesis_builder import HypothesisBuilder
prior = HypothesisBuilder(self.store, self.gateway, self.joint_config)._history(reader, checkpoint_chain(reader), revision)
context["joint_generation"] = {"policy": self.joint_config.policy(), "prior_generations": prior}
if self.joint_version==2:context["joint_generation"].update(version=2, recovery_version=1)
if self.joint_version==2:context["joint_generation"].update(version=2, recovery_version=2)
prompt_version = self.prompt.version(context)
chain = checkpoint_chain(reader)
complete_id = f"case-interpreter:v1:revision:{revision}:complete"
@@ -51,7 +51,7 @@ def build_request(context):
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") + ("\nFor every described_behavior statement include verification=not_independently_verified. Each premise reference must also occur in its proposal dependency lists; declare the union.\n" if settings.get("recovery_version")==1 else "")},
"messages": [{"role": "system", "content": files("viano_bug_analysis.llm.prompts").joinpath(version + ".txt").read_text(encoding="utf-8") + ("\nFor every described_behavior statement include verification=not_independently_verified. Each premise reference must also occur in its proposal dependency lists; declare the union.\n" if settings.get("recovery_version") in (1,2) else "")},
{"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}})}]}
@@ -73,6 +73,8 @@ def interpretation_payload(call, context, reader=None, depth=0):
from .joint_v2 import project
return project(reader,call,context,depth)
if is_joint(call):
from .joint_v2 import joint_document
payload = joint_document(payload,context)
payload = payload.get("interpretation") if isinstance(payload, dict) else None
payload = normalize_block(payload, output_schema(context))
return payload
+70 -5
View File
@@ -60,7 +60,53 @@ def get(value, path):
def recovery_enabled(context):
return enabled(context) and context['joint_generation'].get('recovery_version') == 1
return enabled(context) and context['joint_generation'].get('recovery_version') in (1,2)
def structural_recovery(context):
return enabled(context) and context['joint_generation'].get('recovery_version') == 2
def joint_document(document, context):
"""Recover one exact, unambiguous wrapper error; keep the captured answer intact."""
if not structural_recovery(context) or not isinstance(document,dict):return document
inner=document.get('hypotheses')
if not isinstance(inner,dict) or 'interpretation' not in inner:return document
# Never select between competing blocks, including null or malformed duplicates.
if 'interpretation' in document:raise InterpretationError('INTERPRETATION_SCHEMA')
if set(document)!={'hypotheses'} or set(inner)!={'interpretation','hypotheses'}:return document
if not isinstance(inner['interpretation'],dict) or not isinstance(inner['hypotheses'],list):return document
return {'interpretation':copy.deepcopy(inner['interpretation']),
'hypotheses':{'hypotheses':copy.deepcopy(inner['hypotheses'])}}
def entity_citations(statement, context):
"""Existing identity/mention citations eligible for preservation, not new evidence."""
from .contracts import entity_binding_sources
if not isinstance(statement,dict):return []
sid=statement.get('entity_id')
if not isinstance(sid,str):return []
source=context['sources'].get(sid,{})
if source.get('kind')!='entity':return []
data=source['data']
if data.get('citation_policy') not in {'linked_capture_v2','grouped_capture_v3'}:return []
eligible=entity_binding_sources(sid,source,context['sources']) | set(data['mention_source_ids'])
eligible.update(b['source_id'] for b in data.get('name_bindings',[]))
refs=statement.get('source_ids')
if not isinstance(refs,list) or not all(isinstance(s,str) for s in refs):return []
return [s for s in refs if s in eligible and s in context['sources']]
def preserve_entity_citations(original, replacement, context):
"""Restore only existing citations for the identical entity and identical claim."""
result=copy.deepcopy(replacement)
if not structural_recovery(context) or not isinstance(original,dict) or not isinstance(result,dict):return result
if any(original.get(k)!=result.get(k) for k in ('entity_id','reason')):return result
refs=result.get('source_ids')
if not isinstance(refs,list) or not all(isinstance(s,str) for s in refs):return result
for sid in entity_citations(original,context):
if sid not in refs:refs.append(sid)
return result
def complete_dependencies(raw, interpretation, context):
@@ -222,6 +268,16 @@ def repair_request(request,reader,failure,feedback):
"statements":selected,"hypotheses":raw,"declared_interpretation_inputs":dependencies,
"sources":{s:context["sources"][s] for s in sorted(source_ids) if s in context["sources"]},
"interpretation_contract":schemas(context)[0],"review_contract":review}
extra_instruction=''
if structural_recovery(context):
keep={path:entity_citations(value,context) for path,value in selected.items() if path.startswith('/relevant_entities/')}
keep={path:refs for path,refs in keep.items() if refs}
if keep:
body['preserve_identity_citations']=keep
extra_instruction+='\nFor an unchanged entity_id and reason, retain listed preserve_identity_citations. They already support that exact identity statement. Do not replace identity evidence with report-only references.'
if raw is None:
body['review_contract']={'type':'array','maxItems':0}
extra_instruction+='\nNo reusable hypotheses are present. Return reviews=[]; repair only the listed interpretation statements. Do not reconstruct or review missing hypotheses.'
from .model_context import compact_context,model_json
from .json_consolidation import consolidate
binding={k:body.pop(k) for k in ('source_ref','source_sha256')}
@@ -231,7 +287,7 @@ def repair_request(request,reader,failure,feedback):
"output_schema":{"type":"object","required":["patches"],"properties":{
"patches":{"type":"array","minItems":len(paths),"maxItems":len(paths),"items":patch},
"reviews":{}},"additionalProperties":False},
"messages":[{"role":"system","content":files("viano_bug_analysis.llm.prompts").joinpath(REPAIR+'.txt').read_text(encoding="utf-8")+"\n"+encoding},
"messages":[{"role":"system","content":files("viano_bug_analysis.llm.prompts").joinpath(REPAIR+'.txt').read_text(encoding="utf-8")+"\n"+encoding+extra_instruction},
{"role":"user","content":encoded}]}
@@ -269,11 +325,19 @@ def project(reader,call,context,depth=0):
result=copy.deepcopy(original)
for patch in sorted(patches,key=lambda p:(p["path"].split('/')[1], int(p["path"].split('/')[-1]) if p["path"].split('/')[-1].isdigit() else -1),reverse=True):
parts=patch["path"].strip('/').split('/')
if len(parts)==1:result[parts[0]]=copy.deepcopy(patch["replacement"])
if len(parts)==1:
value=copy.deepcopy(patch['replacement'])
if parts[0]=='relevant_entities' and isinstance(value,list) and isinstance(original.get(parts[0]),list):
for i,entity in enumerate(value):
if i<len(original[parts[0]]):value[i]=preserve_entity_citations(original[parts[0]][i],entity,context)
result[parts[0]]=value
elif len(parts)==2 and isinstance(result[parts[0]],list):
i=int(parts[1])
if patch["replacement"] is None:del result[parts[0]][i]
else:result[parts[0]][i]=copy.deepcopy(patch["replacement"])
else:
value=patch['replacement']
if parts[0]=='relevant_entities':value=preserve_entity_citations(original[parts[0]][i],value,context)
result[parts[0]][i]=copy.deepcopy(value)
else:raise InterpretationError("INTERPRETATION_PATCH_SCOPE")
return result
@@ -282,7 +346,8 @@ def candidate(reader,call,context,strict=True,depth=0):
from .joint_planning import interpretation_payload, normalize_block, schemas
version=call["outcome"].get("metadata",{}).get("prompt_version")
if version==VERSION:
raw=call["outcome"].get("document",{}).get("hypotheses")
document=joint_document(call["outcome"].get("document",{}),context)
raw=document.get('hypotheses') if isinstance(document,dict) else None
raw=normalize_block(raw,schemas(context)[1])
if not Draft202012Validator(schemas(context)[1]).is_valid(raw):
if strict:raise HypothesisError("HYPOTHESIS_SCHEMA")
@@ -122,7 +122,10 @@
]
},
"recovery_version": {
"const": 1
"enum": [
1,
2
]
}
},
"required": [
@@ -131,7 +131,10 @@
]
},
"recovery_version": {
"const": 1
"enum": [
1,
2
]
}
},
"required": [
@@ -0,0 +1,24 @@
# BUG-000020: misplaced joint block and lost identity citations
Original saved calls from joint-recovery-20261020, unchanged bytes; bounded context only.
The initial reply nested interpretation inside hypotheses, alongside the hypothesis array.
The projection failed to find the top-level interpretation and unnecessarily regenerated it.
A precise lossless wrapper repair can move the sole interpretation to the top level while
keeping every statement and hypothesis unchanged. Conflicting duplicate blocks, unknown
siblings and malformed ambiguous structures must not be silently discarded.
The full interpretation repair omitted Q0001 and needed a lookup/mention citation fix.
The next scoped patch added Q0001 and mention sources for N0005, but dropped matching
lookup citations F0029/F0031/F0032. These are already present in the previous N0005 statement,
whose entity_id AND reason remained unchanged. A derived projection can preserve existing
eligible identity/mention citations only for that exact unchanged claim. Changed identity or
reason must still be validated without such restoration. Unknown replacement references
must not be removed or silently accepted. Empty reviews are expected when no reusable
hypotheses exist; no semantic review should be demanded for missing candidates.
Offline regression confirms that the recovered initial interpretation passes full validation.
Its original hypotheses also pass the complete proposal validation with existing explicit
dependency normalization. Thus no correction call is needed for this recorded planning answer.
This does not guarantee correction-free replies on future live runs or success of a whole
investigation. The later scoped-patch projection independently passes interpretation validation
after restoring only existing eligible citations for the unchanged identity statement.
@@ -0,0 +1 @@
{"analysis_revision": 1, "case_id": "BUG-000020", "code": null, "config_hash": "249a08e844d8a62c6b202c0bf856d2fa67f9c77f7a1f03174e79f874bf24ac69", "format": "viano-llm-call", "format_version": 1, "input_request_hash": "9bf55d9b8cf04d7cc82ac8c869122a2f22eae9f52d6654d2f321f2640d621609", "operation": "llm-round", "operation_key": "cdf9fddd5dccf7c6859f2f4c5aa65e46778d6d44e446cf579fa6b6f462b9b85c", "outcome": {"document": {"behavior_gap": null, "confirmed_technical_facts": [{"source_ids": ["F0001", "F0002", "F0003", "F0004", "F0005", "F0006", "F0007", "F0008", "F0009", "F0010", "F0011"], "text": "Reproduction execution at Ironforge Brewfest (map 0, zone 1, area 1, coordinates -5178.33, -577.744, 397.622) for diagnostic focus quest 11117 resulted in reproduced failure."}, {"source_ids": ["F0014", "F0015", "F0019"], "text": "Creature entry 23486 is Goldark Snipehunter, configured with SmartAI to cast Synthebrew Goggles creation on gossip option 0."}, {"source_ids": ["F0016", "F0018", "F0024", "F0025", "F0026"], "text": "Quest 11117 is titled 'Catch the Wild Wolpertinger!', starts with item 32907, and requires item 32906 and item 32907 in quest_template."}, {"source_ids": ["F0029", "F0031"], "text": "Creature template entry 23487 corresponds to 'Wild Wolpertinger' with SmartAI."}], "contradictions": [], "described_behavior": [{"basis": "local_description", "source_ids": ["F0017"], "text": "Quest 11117 description states that Goldark Snipehunter asks the player to capture 5 Stunned Wolpertingers using the Wolpertinger Net, visible while intoxicated or wearing Synthebrew Goggles.", "verification": "not_independently_verified"}], "expected_behavior": [], "failure_stage": {"source_ids": ["F0001", "O0002"], "text": "Creature spawning at Ironforge Brewfest location during quest 11117."}, "first_known_divergence": null, "interpretation_summary": {"source_ids": ["B0002", "F0001", "F0017", "O0002"], "text": "Reproduction confirms that Wolpertingers do not spawn at the Brewfest grounds in Ironforge for quest 11117 ('Catch the Wild Wolpertinger!'), even while wearing Synthebrew Goggles, while authoritative expected behavior specifications remain missing."}, "investigation_direction": [{"source_ids": ["F0001", "F0017", "F0029"], "text": "Investigate spawn definitions, visibility conditions, and event linkage for Wild Wolpertinger (entry 23487) during the Brewfest game event."}, {"source_ids": ["O0002"], "text": "Compare spawn mechanisms between the functioning Horde Brewfest quest counterpart in Durotar/Orgrimmar and the Ironforge location."}], "observed_behavior": [{"source_ids": ["B0002", "O0001"], "text": "Wolpertinger do not spawn."}, {"source_ids": ["O0002", "R0001"], "text": "At Ironforge Brewfest, no Wolpertingers spawn even when wearing the required goggles, whereas the Horde equivalent in Orgrimmar reportedly spawned Wolpertingers correctly."}, {"source_ids": ["O0003", "R0001"], "text": "Wolpertingers indeed do not spawn upon testing."}], "provisional_behavior_gap": {"basis": "local_description", "source_ids": ["F0017", "O0002"], "status": "provisional", "text": "Quest text describes capturing Wild Wolpertingers spotted via Synthebrew Goggles or intoxication, but at Ironforge Brewfest no Wolpertingers spawn."}, "relevant_entities": [{"entity_id": "N0001", "reason": "Goldark Snipehunter (entry 23486), quest giver and reported primary entity.", "source_ids": ["F0014", "F0015"]}, {"entity_id": "N0002", "reason": "Recorded entity: item 32906. Display name and functional identity are not verified.", "source_ids": ["F0016", "F0025"]}, {"entity_id": "N0003", "reason": "Recorded entity: item 32907. Display name and functional identity are not verified.", "source_ids": ["F0016", "F0024", "F0026"]}, {"entity_id": "N0004", "reason": "Quest 11117 ('Catch the Wild Wolpertinger!'), the diagnostic focus of reproduction.", "source_ids": ["F0009", "F0011", "F0016", "F0018"]}, {"entity_id": "N0005", "reason": "Wild Wolpertinger (entry 23487), the creature subject of quest 11117 reported missing from the Brewfest spawn area.", "source_ids": ["F0029", "F0031", "F0032"]}], "tester_assumptions": [], "unknowns": [{"source_ids": ["D0004"], "text": "Independent expected behavior documentation is missing from case records."}, {"source_ids": ["D0005", "F0030"], "text": "The phrase 'Stunned Wolpertingers' does not match any creature template entry in the world database snapshot."}]}, "metadata": {"attempts": [], "cache_created_tokens": 0, "cache_status": "below_minimum", "cached_input_tokens": null, "cost_currency": "USD", "elapsed_seconds": 26.828, "estimated_cache_storage_cost": 0.0, "estimated_cached_input_cost": null, "estimated_cost": null, "estimated_input_cost": null, "estimated_output_cost": 0.017445, "input_tokens": 14078, "model": "gemini-3.8-flash", "output_tokens": 4652, "prompt_version": "case_plan_interpretation_repair_v1", "provider": "gemini-native", "reasoning_tokens": 2854, "uncached_input_tokens": null}, "response_assessment"Line truncated
@@ -0,0 +1 @@
{"analysis_revision": 1, "case_id": "BUG-000020", "code": null, "config_hash": "249a08e844d8a62c6b202c0bf856d2fa67f9c77f7a1f03174e79f874bf24ac69", "format": "viano-llm-call", "format_version": 1, "input_request_hash": "ce78c74c5d36746c80074d08392df0e645a3c143fd3a334d25961bf2dbd1b1c3", "operation": "llm-round", "operation_key": "2cd36795173fe6d547ff571814187ce1b12b3a7ec6accd4ebd98869d46bad248", "outcome": {"document": {"patches": [{"path": "/relevant_entities/4", "replacement": {"entity_id": "N0005", "reason": "Wild Wolpertinger (entry 23487), the creature subject of quest 11117 reported missing from the Brewfest spawn area.", "source_ids": ["B0002", "F0017", "O0001", "O0002"]}}, {"path": "/unknowns", "replacement": [{"source_ids": ["D0004", "Q0001"], "text": "Independent expected behavior documentation is missing from case records."}, {"source_ids": ["D0005", "F0030"], "text": "The phrase 'Stunned Wolpertingers' does not match any creature template entry in the world database snapshot."}]}], "reviews": []}, "metadata": {"attempts": [], "cache_created_tokens": 0, "cache_status": "below_minimum", "cached_input_tokens": null, "cost_currency": "USD", "elapsed_seconds": 96.547, "estimated_cache_storage_cost": 0.0, "estimated_cached_input_cost": null, "estimated_cost": null, "estimated_input_cost": null, "estimated_output_cost": 0.05642625, "input_tokens": 6256, "model": "gemini-3.8-flash", "output_tokens": 15047, "prompt_version": "case_plan_statement_repair_v2", "provider": "gemini-native", "reasoning_tokens": 14851, "uncached_input_tokens": null}, "response_assessment": {"decision": "protocol_valid", "document_retained": true, "raw_retained": true, "version": 1}, "response_capture": {"bytes": 600, "complete": true, "retained": true, "sha256": "370d2759efe563244d49f4fc9fe8e4b7baa74697c830f2b1e81e70bea0a72c20", "text": "{\"patches\": [{\"path\": \"/relevant_entities/4\", \"replacement\": {\"entity_id\": \"N0005\", \"reason\": \"Wild Wolpertinger (entry 23487), the creature subject of quest 11117 reported missing from the Brewfest spawn area.\", \"source_ids\": [\"B0002\", \"F0017\", \"O0001\", \"O0002\"]}}, {\"path\": \"/unknowns\", \"replacement\": [{\"source_ids\": [\"D0004\", \"Q0001\"], \"text\": \"Independent expected behavior documentation is missing from case records.\"}, {\"source_ids\": [\"D0005\", \"F0030\"], \"text\": \"The phrase 'Stunned Wolpertingers' does not match any creature template entry in the world database snapshot.\"}]}], \"reviews\": []}", "version": 1}}, "package_id": "trial-joint-recovery-BUG-000020-20261020", "phase": "INTERPRETING_CASE", "request_ref": "llm/operations/2cd36795173fe6d547ff571814187ce1b12b3a7ec6accd4ebd98869d46bad248/request.json", "result_ref": "llm/operations/2cd36795173fe6d547ff571814187ce1b12b3a7ec6accd4ebd98869d46bad248/round-0001.json", "retry_not_before": null, "rounds": 1, "status": "SUCCESS"}
@@ -0,0 +1 @@
{"analysis_revision": 1, "case_id": "BUG-000020", "code": null, "config_hash": "249a08e844d8a62c6b202c0bf856d2fa67f9c77f7a1f03174e79f874bf24ac69", "format": "viano-llm-call", "format_version": 1, "input_request_hash": "16033a280958d149282f55c21ef5254292c29b6bab46ea8e806c76266be94cc2", "operation": "llm-round", "operation_key": "572afccbd4034d9c5485045cb4bb28b61fdd40e48dc8a3fab67cfc17e997cd65", "outcome": {"document": {"hypotheses": {"hypotheses": [{"distinction": "Focuses on missing or improperly phased world spawns for creature 23487 (Wild Wolpertinger) in the Dun Morogh / Ironforge Brewfest area compared to Durotar.", "expected_evidence": ["Database inspection of table creature shows zero spawns or event-gated spawns with missing game_event linkage for entry 23487 outside Ironforge."], "falsifying_evidence": ["World spawns for entry 23487 exist in Dun Morogh with active event associations and valid coordinates matching the reproduction area."], "interpretation_paths": ["/interpretation/observed_behavior/1", "/interpretation/provisional_behavior_gap"], "mechanism": "Wild Wolpertingers (entry 23487) lack active creature spawn definitions or appropriate game_event_creature associations for Dun Morogh (zone 1), preventing them from appearing in the game world near Ironforge during Brewfest even when the player has the required detection aura.", "open_questions": ["Are there records for creature 23487 in table creature or game_event_creature for map 0 / zone 1?", "Does the Horde counterpart quest use a separate creature entry or separate spawn records in Durotar?"], "premises": [{"interpretation_paths": ["/interpretation/observed_behavior/1"], "kind": "grounded", "source_ids": ["O0002"], "text": "The tester observed that no Wolpertingers spawn at Ironforge Brewfest while wearing the glasses, unlike the working Horde counterpart in Orgrimmar."}, {"interpretation_paths": ["/interpretation/provisional_behavior_gap"], "kind": "unverified", "source_ids": ["F0017", "O0002"], "text": "The Ironforge Brewfest area lacks active creature table spawn records or game event links for Wild Wolpertinger (entry 23487)."}], "rationale": "The tester reported that Wolpertingers do not appear at the Ironforge Brewfest grounds despite wearing the Synthebrew Goggles, whereas the Horde equivalent works properly. The most direct cause of NPC absence in a specific faction area is missing or unlinked spawn records in the creature or game_event_creature tables for the Dun Morogh coordinates.", "source_ids": ["O0002", "F0017"], "title": "Missing Dun Morogh Spawn Records for Wild Wolpertingers"}, {"distinction": "Focuses on visibility phase, stealth, or aura-detection flag mismatches rather than absence of spawn rows in the creature table.", "expected_evidence": ["Creature template or spawn definitions for entry 23487 require an aura or invisibility phase that is not granted by item 32907 / spell 66592 on Alliance."], "falsifying_evidence": ["Creature 23487 has default visibility flags or phase masks that match the aura granted by Alliance Synthebrew Goggles."], "interpretation_paths": ["/interpretation/observed_behavior/1", "/interpretation/confirmed_technical_facts/3"], "mechanism": "Wild Wolpertingers are spawned in Dun Morogh but configured with unit_flags, phase masks, or stealth detection settings that do not match the specific drunken/goggles detection aura applied by Alliance quest items, making them invisible to players.", "open_questions": ["What invisibility or stealth flag is used on creature template 23487?", "Does spell 66592 apply the exact stealth detection aura required to see entry 23487?"], "premises": [{"interpretation_paths": ["/interpretation/confirmed_technical_facts/3"], "kind": "grounded", "source_ids": ["F0017"], "text": "Quest description notes that players must be intoxicated or wearing Synthebrew Goggles to see Wolpertingers."}, {"interpretation_paths": ["/interpretation/observed_behavior/1"], "kind": "unverified", "source_ids": ["O0002"], "text": "Alliance Synthebrew Goggles or local intoxication may grant an aura that does not match the detection requirements on the Dun Morogh Wolpertingers."}], "rationale": "Wolpertingers are intended to be invisible unless the player is drunk or wearing Synthebrew Goggles. If the Alliance goggles (created via spell 66592 from Goldark) or Dun Morogh creatures have a mismatched invisibility phase or aura requirement, players will observe that they do not appear even if the spawns exist in the database.", "source_ids": ["O0002", "F0017", "F0019"], "title": "Detection Aura or Invisibility Phasing Mismatch"}, {"distinction": "Focuses on dynamic script-triggered spawning or event script failures instead of static creature template and spawn table defects.", "expected_evidence": ["A SmartAI or spell script attached to quest accept, gossip, or area triggers intended to spawn Wolpertingers is missing or fails on execution."], "falsifying_evidence": ["Wolpertingers are purely staticLine truncated
@@ -0,0 +1 @@
{"case": {"analysis_revision": 1, "case_id": "BUG-000020", "package_id": "trial-joint-recovery-BUG-000020-20261020", "project": {"profile": "vcore", "type": "wow"}, "source_generation": 3}, "case_ref": "case/revisions/0001/case.json", "case_sha256": "7c9e13e923c9d6ecbfe8d4cc0936ac78ec2ec7e4ef97760d07cf0444c83cd4ee", "context_selection": "phase_selection_v1", "expected_behavior_completeness": "missing", "expected_behavior_provenance": "none", "format": "viano-case-interpretation-context", "format_version": 1, "joint_generation": {"policy": {"budget_unit": "investigation_units", "format": "viano-hypothesis-policy", "format_version": 1, "initial_budget": 100, "max_hypotheses": 5, "min_hypotheses": 3}, "prior_generations": [], "recovery_version": 1, "version": 2}, "model_encoding": "shared_json_v1", "sources": {"B0001": {"case_pointer": "/bug/reports/0", "data": {"origin": {"archive_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/archive.tar.gz", "json_pointer": "/0", "member_name": "BUG-000020-20261008-130429/bug/reports.json", "source_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/members/BUG-000020-20261008-130429/bug/reports.json"}, "source_ref": "case/inputs/vcore/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/report-32.json", "summary": null, "title": null}, "kind": "report"}, "B0002": {"case_pointer": "/bug/reports/1", "data": {"origin": {"archive_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/archive.tar.gz", "json_pointer": "", "member_name": "BUG-000020-20261008-130429/bug/bug.json", "source_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/members/BUG-000020-20261008-130429/bug/bug.json"}, "source_ref": "case/inputs/vcore/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/bug-record.json", "summary": "Wolpertinger spawnen nicht", "title": "Wolpertinger spawnen nicht"}, "kind": "report"}, "B0003": {"case_pointer": "/bug/reports/2", "data": {"source_ref": "case/inputs/vcore/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/source-quality.json", "summary": null, "title": null}, "kind": "report"}, "D0001": {"case_pointer": "/data_quality/issues/0", "data": {"code": "SOURCE_TIME_UNQUALIFIED", "message": "Source time has no usable timezone; retained without conversion", "refs": [{"json_pointer": "/0/description", "source_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/members/BUG-000020-20261008-130429/bug/reports.json"}]}, "kind": "data_quality"}, "D0002": {"case_pointer": "/data_quality/issues/1", "data": {"code": "SOURCE_TIME_UNQUALIFIED", "message": "Source time has no usable timezone; retained without conversion", "refs": [{"json_pointer": "/0/observation", "source_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/members/BUG-000020-20261008-130429/bug/reproductions.json"}]}, "kind": "data_quality"}, "D0003": {"case_pointer": "/data_quality/issues/2", "data": {"code": "SOURCE_TIME_UNQUALIFIED", "message": "Source time has no usable timezone; retained without conversion", "refs": [{"json_pointer": "/1/data_json", "source_ref": "original/vcore/imports/768ba7813ccc3ac656328e895d4abcc8d56822c2976a149a95e99cf91f8bb096/wowdiag/members/BUG-000020-20261008-130429/bug/reproduction_events.json"}]}, "kind": "data_quality"}, "D0004": {"case_pointer": "/data_quality/issues/3", "data": {"code": "EXPECTED_BEHAVIOR_MISSING", "message": "Expected-behavior completeness is missing", "refs": []}, "kind": "data_quality"}, "D0005": {"case_pointer": "/entity_resolution/unresolved/1", "data": {"capture_ref": "analysis/interpretations/0001/entity-resolution/captures/cad7b25d13e299feaf7ea231e23fe5af91492fc2a6a8bb6ae0476bbb9bc40de2.json", "citation_policy": "grouped_capture_v3", "code": "ENTITY_RESOLUTION_UNRESOLVED", "meaning": "scoped_lookup_uncertainty_not_global_identity_failure", "reason": "Capture V0002 returned no rows for 'Stunned Wolpertingers' in creature_template, representing an unmapped outcome phrase rather than an entity record in the database snapshot.", "status": "no_match", "term": "Stunned Wolpertingers"}, "kind": "data_quality"}, "D0006": {"case_pointer": "/interpretation_quality/policy", "data": {"citation_policy": "grouped_capture_v3", "code": "DESCRIPTION_PROVENANCE_POLICY", "meaning": "local_descriptions_are_not_independently_verified_expectations"}, "kind": "data_quality"}, "D0007": {"case_pointer": "/interpretation_quality/model_context", "data": {"model_context_policy": "compact_storage_v1"}, "kind": "data_quality"}, "D0008": {"case_pointer": "/interpretation_quality/name_bindings", "data": {"name_binding_policy": "typed_name_bindings_v1"}, "kind": "data_quality"}, "D0009": {"case_pointer": "/interpretation_quality/iLine truncated
@@ -0,0 +1,6 @@
{
"case_plan_interpretation_repair_v1.json": "4567683fb436d3467c99c720e2721dbf88e9d886927688310daa167c36654bf5",
"case_plan_statement_repair_v2.json": "e749e5545c6403ea2f543674d06d8092490ecfaa80e9c8d74c412f1597cc7768",
"case_plan_v2.json": "2ff4e170dc33b33d58ea7c44e497dfc10302a8170248b5789abe67d6107fb72c",
"context.json": "10f11d5bbeb61d986b6384e702434737be297ba7a148afb81d5725514544258e"
}
+118
View File
@@ -292,3 +292,121 @@ def test_added_dependency_prevents_false_independence_after_patch(built_case):
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']