← Files VeraARCHIVED FILE
modules/deep-research-validator/scripts/apply_review_edits.py
18.5 KB · Oct 2, 2026 · 00:29 UTC
from __future__ import annotations
import argparse
import json
import shutil
import sys
from pathlib import Path
from typing import Any
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
for _vendor_root in (
PLUGIN_ROOT / "vendor" / "modules",
PLUGIN_ROOT.parent.parent / "vendor" / "modules",
PLUGIN_ROOT.parent / "_shared" / "vendor" / "modules",
):
if (_vendor_root / "vera_assurance").is_dir():
if str(_vendor_root) not in sys.path:
sys.path.insert(0, str(_vendor_root))
break
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from package_validation import ( # noqa: E402
build_audit,
render_validation_package,
)
from vera_assurance import ( # noqa: E402
AssuranceContractError,
load_client_workflow_context_for_output,
)
__all__ = ["apply_review_edits", "main"]
FINAL_HANDOFF_ACTION = (
"Use final_artifacts.json as the reviewed artifact gallery for handoff."
)
COMPLETE_REVIEW_ACTION = "Complete remaining review decisions before final handoff."
REGENERATE_ANSWER_ACTION = (
"Regenerate the reviewed or corrected answer semantically from the accepted "
"claim dispositions, then rerun validation packaging."
)
VALIDATED_DOCUMENT_DOCX = "validated_document.docx"
def clean_text(value: object) -> str:
"""Return a stripped string for safe JSON field comparison."""
return value.strip() if isinstance(value, str) else ""
def _read_json(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise ValueError(f"Expected JSON object: {path}")
return payload
def _write_json(path: Path, payload: dict[str, Any]) -> None:
path.write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
def _run_output_file(output_dir: Path, path: Path, label: str) -> Path:
"""Resolve one existing file and keep it inside the selected run output."""
resolved_output = output_dir.expanduser().resolve()
try:
resolved = path.expanduser().resolve(strict=True)
except OSError as exc:
raise ValueError(f"{label} is unavailable: {exc}") from exc
if not resolved.is_file() or not resolved.is_relative_to(resolved_output):
raise ValueError(f"{label} must be a file inside the run output")
return resolved
def _eligible_claim_fix_effect(effect: dict[str, Any]) -> bool:
"""Return whether a structured claim edit should refresh package Markdown."""
if effect.get("action") != "edit":
return False
if effect.get("artifact_update") != "structured_artifact_updated":
return False
if clean_text(effect.get("target_artifact")) != "claims_review.json":
return False
if clean_text(effect.get("target_field")) != "proposed_fix":
return False
return bool(clean_text(effect.get("edit_value")))
def _pending_native_paths(effects: list[dict[str, Any]]) -> list[str]:
paths: list[str] = []
for effect in effects:
if not effect.get("requires_native_regeneration"):
continue
raw_paths = effect.get("derived_native_regeneration_paths")
if not isinstance(raw_paths, list):
raw_paths = [effect.get("target_artifact")]
for raw_path in raw_paths:
text = clean_text(raw_path)
if text:
paths.append(text)
return list(dict.fromkeys(paths))
def _safe_item_id(value: object) -> str:
text = clean_text(value) or "item"
cleaned = "".join(char if char.isalnum() or char in "._-" else "-" for char in text)
return cleaned.strip("-") or "item"
def _backup_file(output_dir: Path, item_id: str, target_name: str) -> dict[str, Any]:
source = output_dir / target_name
if not source.exists():
return {}
suffix = source.suffix or ".md"
relative = (
Path("revisions")
/ "originals"
/ f"{source.stem}__{_safe_item_id(item_id)}{suffix}"
)
target = output_dir / relative
target.parent.mkdir(parents=True, exist_ok=True)
if not target.exists():
shutil.copy2(source, target)
return {
"path": relative.as_posix(),
"kind": suffix.lstrip(".") or "file",
"status": "backup_original",
"source_artifact": target_name,
"item_id": item_id,
}
def _upsert_output(outputs: list[dict[str, Any]], record: dict[str, Any]) -> None:
path = record.get("path")
for index, output in enumerate(outputs):
if isinstance(output, dict) and output.get("path") == path:
outputs[index] = {**output, **record}
return
outputs.append(record)
def _resolve_input_path(
output_dir: Path,
run_intake: dict[str, Any],
file_name: str,
*,
prefer_output_dir: bool = False,
run_root: Path | None = None,
) -> Path:
output_candidate = output_dir / file_name
if prefer_output_dir and output_candidate.exists():
return output_candidate
input_paths = run_intake.get("input_paths")
if isinstance(input_paths, list):
for raw_path in input_paths:
if not isinstance(raw_path, str):
continue
path = Path(raw_path)
if path.name != file_name:
continue
if run_intake.get("path_reference") == "run_root_relative":
if run_root is None or path.is_absolute() or ".." in path.parts:
raise ValueError("Managed Answer Validator input path is invalid.")
resolved_root = run_root.expanduser().resolve(strict=True)
candidate = (resolved_root / path).resolve(strict=True)
if not candidate.is_relative_to(resolved_root):
raise ValueError(
"Managed Answer Validator input escapes the current run."
)
candidates = [candidate]
else:
candidates = [path] if path.is_absolute() else [output_dir / path]
candidates.append(output_candidate)
for candidate in candidates:
if candidate.exists():
return candidate
return output_candidate
def _application_status(applied: dict[str, Any]) -> str:
if int(applied.get("blocker_count") or 0) > 0:
return "blocked"
if int(applied.get("semantic_regeneration_count") or 0) > 0:
return "revision_required"
if int(applied.get("native_regeneration_count") or 0) > 0:
return "partial_review_applied"
if int(applied.get("decision_count") or 0) < int(applied.get("item_count") or 0):
return "partial_review_applied"
return "final_ready"
def _next_actions(current: list[Any], status: str) -> list[str]:
next_actions = [clean_text(action) for action in current if clean_text(action)]
if status == "final_ready":
next_actions.append(FINAL_HANDOFF_ACTION)
elif status == "partial_review_applied":
next_actions.append(COMPLETE_REVIEW_ACTION)
elif status == "revision_required":
next_actions.append(REGENERATE_ANSWER_ACTION)
return list(dict.fromkeys(next_actions))
def _required_package_text(candidate_effects: list[dict[str, Any]]) -> list[str]:
fragments = [
"# Answer Validation Record",
"## Assurance Boundary",
"## Answer Contract",
"## Answer-Contract Review",
"## Review Coverage",
"## Document Inventory",
"## Claim Assessments",
]
for effect in candidate_effects:
edit_value = clean_text(effect.get("edit_value"))
if edit_value:
fragments.append(edit_value)
return list(dict.fromkeys(fragments))
def apply_review_edits(
output_dir: Path,
applied_decisions_path: Path,
final_artifacts_path: Path,
*,
client_engagement: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Refresh deterministic package artifacts after explicit claim JSON edits."""
output_dir = output_dir.resolve()
applied_decisions_path = applied_decisions_path.resolve()
final_artifacts_path = final_artifacts_path.resolve()
run_intake_path = output_dir / "run_intake.json"
claims_review_path = output_dir / "claims_review.json"
validation_audit_path = output_dir / "validation_audit.json"
validation_package_path = output_dir / "validation_package.md"
answer_contract_path = output_dir / "answer_contract.json"
applied = _read_json(applied_decisions_path)
final_artifacts = _read_json(final_artifacts_path)
effects = [
effect for effect in applied.get("effects", []) if isinstance(effect, dict)
]
candidate_effects = [
effect for effect in effects if _eligible_claim_fix_effect(effect)
]
if not candidate_effects:
return {
"ok": True,
"updated_effect_count": 0,
"message": "No answer-validation package refresh was required.",
}
if not run_intake_path.exists():
raise FileNotFoundError(run_intake_path)
if not claims_review_path.exists():
raise FileNotFoundError(claims_review_path)
if not answer_contract_path.exists():
raise FileNotFoundError(answer_contract_path)
run_intake = _read_json(run_intake_path)
run_root = (
Path(str(client_engagement["run_root"]))
if client_engagement is not None
else None
)
document_inventory_path = _resolve_input_path(
output_dir,
run_intake,
"document_inventory.json",
run_root=run_root,
)
source_inventory_path = _resolve_input_path(
output_dir,
run_intake,
"source_inventory.json",
run_root=run_root,
)
if not document_inventory_path.exists():
raise FileNotFoundError(document_inventory_path)
if not source_inventory_path.exists():
raise FileNotFoundError(source_inventory_path)
document_inventory = _read_json(document_inventory_path)
source_inventory = _read_json(source_inventory_path)
claims_review = _read_json(claims_review_path)
answer_contract = _read_json(answer_contract_path)
unresolved_changes = [
clean_text(value)
for value in (
claims_review.get("document_revision", {}).get("unresolved_changes", [])
if isinstance(claims_review.get("document_revision"), dict)
else []
)
if clean_text(value)
]
for effect in candidate_effects:
claim_ref = clean_text(effect.get("target_record_id")) or clean_text(
effect.get("item_id")
)
unresolved_changes.append(
f"Regenerate the answer to apply the reviewed correction for claim {claim_ref}."
)
claims_review["document_revision"] = {
"status": "required",
"summary": (
"Reviewer corrections are recorded, but the reviewed or corrected "
"answer has not yet been semantically regenerated."
),
"unresolved_changes": list(dict.fromkeys(unresolved_changes)),
}
claims_review["validated_document"] = ""
_write_json(claims_review_path, claims_review)
audit = build_audit(
document_inventory,
source_inventory,
claims_review,
answer_contract,
source_base_dir=source_inventory_path.parent,
)
package_text = render_validation_package(
document_inventory,
source_inventory,
claims_review,
audit,
"",
)
backup_outputs: list[dict[str, Any]] = []
backup = _backup_file(
output_dir,
clean_text(candidate_effects[0].get("item_id")),
"validation_package.md",
)
if backup:
backup_outputs.append(backup)
for stale_name in ("validated_document.md", VALIDATED_DOCUMENT_DOCX):
backup = _backup_file(
output_dir,
clean_text(candidate_effects[0].get("item_id")),
stale_name,
)
if backup:
backup_outputs.append(backup)
_write_json(validation_audit_path, audit)
validation_package_path.write_text(package_text, encoding="utf-8")
native_outputs: list[dict[str, Any]] = []
native_regenerated_paths: list[str] = []
native_pending = _pending_native_paths(effects)
downstream_paths = ["validation_audit.json", "validation_package.md"]
downstream_paths.extend(native_regenerated_paths)
for effect in candidate_effects:
effect["downstream_regeneration_status"] = "regenerated"
effect["downstream_regenerated_paths"] = downstream_paths
effect["semantic_regeneration_required"] = True
effect["semantic_regeneration_status"] = "required"
applied["effects"] = effects
applied["downstream_regenerated_count"] = len(candidate_effects)
applied["downstream_regenerated_paths"] = downstream_paths
applied["native_regeneration_count"] = len(native_pending)
applied["native_regeneration_paths"] = native_pending
applied["native_regenerated_count"] = len(native_regenerated_paths)
applied["native_regenerated_paths"] = native_regenerated_paths
applied["semantic_regeneration_count"] = len(candidate_effects)
applied["semantic_regeneration_status"] = "required"
original_backup_paths = list(applied.get("original_backup_paths") or [])
for backup_output in backup_outputs:
if backup_output["path"] not in original_backup_paths:
original_backup_paths.append(backup_output["path"])
applied["original_backup_paths"] = original_backup_paths
applied["application_status"] = _application_status(applied)
outputs = [
output
for output in final_artifacts.get("outputs", [])
if isinstance(output, dict)
]
for output in outputs:
if clean_text(output.get("path")) in {
"validated_document.md",
VALIDATED_DOCUMENT_DOCX,
}:
output["status"] = "superseded_requires_semantic_regeneration"
_upsert_output(
outputs,
{
"path": "validation_audit.json",
"kind": "json",
"status": "updated_from_review",
"source_artifact": "claims_review.json",
},
)
_upsert_output(
outputs,
{
"path": "validation_package.md",
"kind": "md",
"status": "updated_from_review",
"source_artifact": "claims_review.json",
"size_bytes": validation_package_path.stat().st_size,
"required_text": _required_package_text(candidate_effects),
"qa_checks": ["nonempty_text", "required_text"],
},
)
for native_output in native_outputs:
_upsert_output(outputs, native_output)
for backup_output in backup_outputs:
_upsert_output(outputs, backup_output)
final_artifacts["outputs"] = outputs
final_artifacts["status"] = applied["application_status"]
final_artifacts["review_status"] = applied["application_status"]
review_application = final_artifacts.setdefault("review_application", {})
if isinstance(review_application, dict):
review_application["application_status"] = applied["application_status"]
review_application["downstream_regenerated_count"] = applied[
"downstream_regenerated_count"
]
review_application["downstream_regenerated_paths"] = downstream_paths
review_application["native_regeneration_count"] = applied[
"native_regeneration_count"
]
review_application["native_regeneration_paths"] = native_pending
review_application["native_regenerated_count"] = applied[
"native_regenerated_count"
]
review_application["native_regenerated_paths"] = native_regenerated_paths
review_application["semantic_regeneration_count"] = applied[
"semantic_regeneration_count"
]
review_application["semantic_regeneration_status"] = "required"
review_application["original_backup_paths"] = original_backup_paths
final_artifacts["next_actions"] = _next_actions(
list(final_artifacts.get("next_actions") or []),
applied["application_status"],
)
_write_json(applied_decisions_path, applied)
_write_json(final_artifacts_path, final_artifacts)
return {
"ok": True,
"updated_effect_count": len(candidate_effects),
"downstream_regenerated_paths": downstream_paths,
"native_regenerated_paths": native_regenerated_paths,
"backup_paths": [backup_output["path"] for backup_output in backup_outputs],
"application_status": applied["application_status"],
"applied_decisions": applied,
"final_artifacts": final_artifacts,
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Apply answer-validation review edits to downstream artifacts."
)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--applied-decisions", type=Path)
parser.add_argument("--final-artifacts", type=Path)
parser.add_argument(
"--client-run-preflight-only",
action="store_true",
help="Validate the owning running customer-folder run without writing.",
)
args = parser.parse_args(argv)
try:
client_context = load_client_workflow_context_for_output(
args.output_dir.expanduser().resolve(),
expected_workflow_id="deep-research-validator",
)
except AssuranceContractError as exc:
parser.error(str(exc))
if args.client_run_preflight_only:
result = {
"ok": True,
"schema_version": client_context["schema_version"],
"workflow_id": client_context["workflow_id"],
"client_run_id": client_context["run_id"],
}
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
return 0
if args.applied_decisions is None or args.final_artifacts is None:
parser.error(
"--applied-decisions and --final-artifacts are required unless "
"--client-run-preflight-only is used"
)
try:
applied_decisions = _run_output_file(
args.output_dir,
args.applied_decisions,
"applied decisions",
)
final_artifacts = _run_output_file(
args.output_dir,
args.final_artifacts,
"final artifacts",
)
except ValueError as exc:
parser.error(str(exc))
result = apply_review_edits(
args.output_dir,
applied_decisions,
final_artifacts,
client_engagement=client_context,
)
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: fda83187337bd3f38f236ee750fad8e300d7e3395e93e97204df74e4c70ee46f