← Files VeraARCHIVED FILE
modules/registro-imprese-sari/scripts/validate_practice_case.py
37.2 KB · Oct 3, 2026 · 06:30 UTC
"""Validate a source-backed Registro Imprese/DIRE practice draft mechanically."""
from __future__ import annotations
import argparse
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Any
from case_core import (
PLUGIN_NAME,
AssuranceContractError,
ensure_safe_output_dir,
load_json_object,
load_running_case_context,
require_case_artifact_run,
safe_identifier,
sha256_file,
validate_iso_date,
validate_official_source_url,
write_private_json,
)
__all__ = ["validate_practice_case", "main"]
LOGGER = logging.getLogger(__name__)
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
ALLOWED_POSITION_TYPES = {
"registre_du_commerce",
"registro_imprese",
"rea",
"agenzia_entrate",
"inps",
"inail",
"suap",
"ivass_rui",
"other",
}
CONFIRMATION_STATUSES = {"confirmed", "needs_professional_review", "unknown"}
ITEM_REVIEW_STATUSES = {"proposed", "confirmed", "not_applicable", "blocked"}
PLAN_ARRAYS = (
"classification_proposals",
"position_matrix",
"dire_steps",
"required_documents",
"application_fields",
"risks",
"missing_information",
)
PROHIBITED_SECRET_KEYS = {
"access_token",
"api_key",
"cie",
"cns",
"cookie",
"cookies",
"credential",
"credentials",
"one_time_code",
"otp",
"passcode",
"password",
"refresh_token",
"session",
"session_cookie",
"session_id",
"signature",
"spid",
"token",
}
MODEL_ROLES = {"ai", "assistant", "codex", "llm", "model"}
def _issue(
issues: list[dict[str, Any]],
*,
code: str,
path: str,
message: str,
severity: str = "error",
) -> None:
issues.append(
{"code": code, "path": path, "message": message, "severity": severity}
)
def _nonempty_text(
value: object,
*,
path: str,
issues: list[dict[str, Any]],
max_length: int = 10_000,
) -> str:
text = str(value or "").strip()
if not text:
_issue(issues, code="missing_text", path=path, message="value is required")
elif len(text) > max_length:
_issue(
issues,
code="text_too_long",
path=path,
message=f"value exceeds {max_length} characters",
)
return text
def _validate_timestamp(
value: object, *, path: str, issues: list[dict[str, Any]]
) -> str:
text = str(value or "").strip()
try:
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
except ValueError:
_issue(
issues,
code="invalid_timestamp",
path=path,
message="timestamp must use ISO 8601",
)
return text
if parsed.tzinfo is None:
_issue(
issues,
code="timestamp_missing_timezone",
path=path,
message="timestamp must include a timezone",
)
return text
def _walk_prohibited_secrets(
value: object,
*,
path: str,
issues: list[dict[str, Any]],
) -> None:
if isinstance(value, dict):
for key, child in value.items():
child_path = f"{path}.{key}" if path else str(key)
normalized_key = (
str(key).strip().casefold().replace("-", "_").replace(" ", "_")
)
if normalized_key in PROHIBITED_SECRET_KEYS:
_issue(
issues,
code="secret_or_session_material_forbidden",
path=child_path,
message=(
"case artifacts must not store credentials, authentication "
"tokens, cookies, signatures, or session material"
),
)
_walk_prohibited_secrets(child, path=child_path, issues=issues)
elif isinstance(value, list):
for index, child in enumerate(value):
_walk_prohibited_secrets(
child,
path=f"{path}[{index}]",
issues=issues,
)
def _optional_text(
value: object,
*,
path: str,
issues: list[dict[str, Any]],
max_length: int = 10_000,
) -> str:
if value is None:
return ""
if not isinstance(value, str):
_issue(issues, code="invalid_text", path=path, message="value must be text")
return ""
text = value.strip()
if len(text) > max_length:
_issue(
issues,
code="text_too_long",
path=path,
message=f"value exceeds {max_length} characters",
)
return text
def _validate_case_intake(
intake: dict[str, Any], *, issues: list[dict[str, Any]]
) -> tuple[str, set[str]]:
jurisdiction = intake.get("jurisdiction", "IT")
if jurisdiction not in {"IT", "CH-GE"}:
_issue(
issues,
code="unsupported_jurisdiction",
path="case_intake.jurisdiction",
message="Choose IT or CH-GE independently of language",
)
if jurisdiction == "CH-GE":
positions = intake.get("requested_operation", {}).get("position_types", [])
if any(
position not in {"registre_du_commerce", "other"} for position in positions
):
_issue(
issues,
code="jurisdiction_position_mismatch",
path="case_intake.requested_operation.position_types",
message="Italian registry and agency positions do not establish a Geneva procedure",
)
if intake.get("schema_version") != "1.0":
_issue(
issues,
code="schema_version_mismatch",
path="case_intake.schema_version",
message="expected 1.0",
)
if intake.get("plugin") != PLUGIN_NAME:
_issue(
issues,
code="plugin_mismatch",
path="case_intake.plugin",
message=f"expected {PLUGIN_NAME}",
)
try:
run_id = safe_identifier(intake.get("run_id"), field="case_intake.run_id")
except ValueError as exc:
_issue(
issues,
code="invalid_run_id",
path="case_intake.run_id",
message=str(exc),
)
run_id = "invalid"
try:
safe_identifier(
intake.get("client_reference"), field="case_intake.client_reference"
)
except ValueError as exc:
_issue(
issues,
code="invalid_client_reference",
path="case_intake.client_reference",
message=str(exc),
)
client_identity = intake.get("client_identity")
has_client_identity = False
if client_identity is not None and not isinstance(client_identity, dict):
_issue(
issues,
code="invalid_client_identity",
path="case_intake.client_identity",
message="client_identity must be an object when provided",
)
elif isinstance(client_identity, dict):
for field in (
"name",
"tax_code",
"vat_number",
"email",
"pec",
"phone",
"address",
):
identity_value = _optional_text(
client_identity.get(field),
path=f"case_intake.client_identity.{field}",
issues=issues,
max_length=2_000,
)
has_client_identity = has_client_identity or bool(identity_value)
try:
validate_iso_date(intake.get("reference_date"), field="reference_date")
except ValueError as exc:
_issue(
issues,
code="invalid_reference_date",
path="case_intake.reference_date",
message=str(exc),
)
fact_ids = {
"CASE-CHAMBER",
"CASE-SUBJECT",
"CASE-ACTIVITY",
"CASE-OPERATION",
"CASE-EFFECTIVE-DATE",
"CASE-PROFESSIONAL-QUESTION",
}
if has_client_identity:
fact_ids.add("CASE-CLIENT")
chamber = intake.get("competent_chamber")
if not isinstance(chamber, dict):
_issue(
issues,
code="missing_object",
path="case_intake.competent_chamber",
message="object is required",
)
else:
_nonempty_text(
chamber.get("tenant"),
path="case_intake.competent_chamber.tenant",
issues=issues,
)
_nonempty_text(
chamber.get("name"),
path="case_intake.competent_chamber.name",
issues=issues,
)
_nonempty_text(
chamber.get("territorial_basis"),
path="case_intake.competent_chamber.territorial_basis",
issues=issues,
)
status = chamber.get("confirmation_status")
if status not in CONFIRMATION_STATUSES:
_issue(
issues,
code="invalid_confirmation_status",
path="case_intake.competent_chamber.confirmation_status",
message="unsupported status",
)
elif status != "confirmed":
_issue(
issues,
code="competent_chamber_unconfirmed",
path="case_intake.competent_chamber.confirmation_status",
message="competent chamber requires professional confirmation",
severity="blocker",
)
subject = intake.get("subject")
if not isinstance(subject, dict):
_issue(
issues,
code="missing_object",
path="case_intake.subject",
message="object is required",
)
else:
_nonempty_text(
subject.get("legal_form"),
path="case_intake.subject.legal_form",
issues=issues,
)
status = subject.get("confirmation_status")
if status not in CONFIRMATION_STATUSES:
_issue(
issues,
code="invalid_confirmation_status",
path="case_intake.subject.confirmation_status",
message="unsupported status",
)
elif status != "confirmed":
_issue(
issues,
code="legal_form_unconfirmed",
path="case_intake.subject.confirmation_status",
message="legal form requires professional confirmation",
severity="blocker",
)
activity = intake.get("activity")
if not isinstance(activity, dict):
_issue(
issues,
code="missing_object",
path="case_intake.activity",
message="object is required",
)
else:
_nonempty_text(
activity.get("description"),
path="case_intake.activity.description",
issues=issues,
)
status = activity.get("classification_status")
if status not in CONFIRMATION_STATUSES:
_issue(
issues,
code="invalid_confirmation_status",
path="case_intake.activity.classification_status",
message="unsupported status",
)
elif status != "confirmed":
_issue(
issues,
code="activity_classification_unconfirmed",
path="case_intake.activity.classification_status",
message="activity description/classification requires professional confirmation",
severity="blocker",
)
operation = intake.get("requested_operation")
if not isinstance(operation, dict):
_issue(
issues,
code="missing_object",
path="case_intake.requested_operation",
message="object is required",
)
else:
_nonempty_text(
operation.get("description"),
path="case_intake.requested_operation.description",
issues=issues,
)
positions = operation.get("position_types")
if not isinstance(positions, list) or not positions:
_issue(
issues,
code="position_types_unresolved",
path="case_intake.requested_operation.position_types",
message="at least one proposed position type is required",
severity="blocker",
)
else:
invalid = sorted({str(item) for item in positions} - ALLOWED_POSITION_TYPES)
if invalid:
_issue(
issues,
code="invalid_position_type",
path="case_intake.requested_operation.position_types",
message="unsupported values: " + ", ".join(invalid),
)
if len(positions) != len(set(map(str, positions))):
_issue(
issues,
code="duplicate_position_type",
path="case_intake.requested_operation.position_types",
message="position types must be unique",
)
effective_date = operation.get("effective_date")
if effective_date is None or str(effective_date).strip() == "":
_issue(
issues,
code="effective_date_unresolved",
path="case_intake.requested_operation.effective_date",
message="effective date is required before a DIRE plan can be finalized",
severity="blocker",
)
else:
try:
validate_iso_date(effective_date, field="effective_date")
except ValueError as exc:
_issue(
issues,
code="invalid_effective_date",
path="case_intake.requested_operation.effective_date",
message=str(exc),
)
status = operation.get("confirmation_status")
if status not in CONFIRMATION_STATUSES:
_issue(
issues,
code="invalid_confirmation_status",
path="case_intake.requested_operation.confirmation_status",
message="unsupported status",
)
elif status != "confirmed":
_issue(
issues,
code="requested_operation_unconfirmed",
path="case_intake.requested_operation.confirmation_status",
message="requested position opening scope requires professional confirmation",
severity="blocker",
)
_nonempty_text(
intake.get("professional_question"),
path="case_intake.professional_question",
issues=issues,
)
return run_id, fact_ids
def _validate_source_manifest(
manifest: dict[str, Any],
*,
run_id: str,
output_dir: Path,
issues: list[dict[str, Any]],
) -> set[str]:
if manifest.get("plugin") != PLUGIN_NAME:
_issue(
issues,
code="source_manifest_plugin_mismatch",
path="official_sources.plugin",
message=f"expected {PLUGIN_NAME}",
)
if manifest.get("run_id") != run_id:
_issue(
issues,
code="source_manifest_run_mismatch",
path="official_sources.run_id",
message="run id must match case intake",
)
sources = manifest.get("sources")
if not isinstance(sources, list) or not sources:
_issue(
issues,
code="official_sources_missing",
path="official_sources.sources",
message="select at least one official source",
)
return set()
source_ids: set[str] = set()
for index, source in enumerate(sources):
path = f"official_sources.sources[{index}]"
if not isinstance(source, dict):
_issue(
issues,
code="source_not_object",
path=path,
message="source must be an object",
)
continue
try:
source_id = safe_identifier(
source.get("source_id"), field=f"{path}.source_id"
)
except ValueError as exc:
_issue(
issues,
code="invalid_source_id",
path=f"{path}.source_id",
message=str(exc),
)
continue
if source_id in source_ids:
_issue(
issues,
code="duplicate_source_id",
path=f"{path}.source_id",
message=f"duplicate source id {source_id}",
)
source_ids.add(source_id)
_nonempty_text(
source.get("title") or source.get("chamber_title"),
path=f"{path}.title",
issues=issues,
)
_nonempty_text(source.get("publisher"), path=f"{path}.publisher", issues=issues)
_nonempty_text(
source.get("territorial_applicability") or source.get("chamber_title"),
path=f"{path}.territorial_applicability",
issues=issues,
)
try:
validate_official_source_url(source.get("official_url"))
except ValueError as exc:
_issue(
issues,
code="invalid_official_source_url",
path=f"{path}.official_url",
message=str(exc),
)
source_timestamp = source.get("retrieved_at") or source.get("registered_at")
if not source_timestamp:
_issue(
issues,
code="source_timestamp_missing",
path=path,
message="source retrieval or registration timestamp is required",
)
else:
_validate_timestamp(
source_timestamp,
path=f"{path}.retrieved_or_registered_at",
issues=issues,
)
if source.get("updated_date"):
try:
validate_iso_date(
source.get("updated_date"), field=f"{path}.updated_date"
)
except ValueError as exc:
_issue(
issues,
code="invalid_source_updated_date",
path=f"{path}.updated_date",
message=str(exc),
)
_nonempty_text(
source.get("selection_status") or source.get("applicability_status"),
path=f"{path}.applicability_status",
issues=issues,
)
artifact_path = source.get("artifact_path")
artifact_hash = source.get("artifact_sha256")
if artifact_path:
absolute = (output_dir / str(artifact_path)).resolve()
if output_dir not in absolute.parents:
_issue(
issues,
code="source_artifact_outside_run",
path=f"{path}.artifact_path",
message="source artifact must stay inside the run directory",
)
elif not absolute.is_file() or absolute.is_symlink():
_issue(
issues,
code="source_artifact_missing",
path=f"{path}.artifact_path",
message="registered source artifact is missing",
)
elif not artifact_hash or sha256_file(absolute) != artifact_hash:
_issue(
issues,
code="source_artifact_integrity_mismatch",
path=f"{path}.artifact_sha256",
message="registered source artifact hash does not match",
)
return source_ids
def _validate_confirmation(
confirmation: object,
*,
path: str,
issues: list[dict[str, Any]],
) -> None:
if not isinstance(confirmation, dict):
_issue(
issues,
code="professional_confirmation_missing",
path=path,
message="confirmed item requires a confirmation object",
)
return
_nonempty_text(
confirmation.get("confirmed_by_id"),
path=f"{path}.confirmed_by_id",
issues=issues,
max_length=120,
)
role = _nonempty_text(
confirmation.get("confirmed_by_role"),
path=f"{path}.confirmed_by_role",
issues=issues,
max_length=120,
)
if role.casefold() in MODEL_ROLES or "professional" not in role.casefold():
_issue(
issues,
code="invalid_confirmation_authority",
path=f"{path}.confirmed_by_role",
message="confirmed items require a professional human reviewer role",
)
_validate_timestamp(
confirmation.get("confirmed_at"),
path=f"{path}.confirmed_at",
issues=issues,
)
_nonempty_text(
confirmation.get("basis"),
path=f"{path}.basis",
issues=issues,
)
def _validate_plan(
plan: dict[str, Any],
*,
run_id: str,
fact_ids: set[str],
source_ids: set[str],
issues: list[dict[str, Any]],
) -> dict[str, int]:
if plan.get("schema_version") != "1.0":
_issue(
issues,
code="schema_version_mismatch",
path="practice_plan.schema_version",
message="expected 1.0",
)
if plan.get("plugin") != PLUGIN_NAME:
_issue(
issues,
code="plugin_mismatch",
path="practice_plan.plugin",
message=f"expected {PLUGIN_NAME}",
)
if plan.get("run_id") != run_id:
_issue(
issues,
code="plan_run_mismatch",
path="practice_plan.run_id",
message="run id must match case intake",
)
_nonempty_text(
plan.get("case_summary"), path="practice_plan.case_summary", issues=issues
)
review_context = plan.get("review_context")
if review_context is not None and not isinstance(review_context, dict):
_issue(
issues,
code="invalid_review_context",
path="practice_plan.review_context",
message="review_context must be an object when provided",
)
all_source_ids = {"CASE-INTAKE", *source_ids}
seen_item_ids: set[str] = set()
review_counts = {status: 0 for status in ITEM_REVIEW_STATUSES}
for array_name in PLAN_ARRAYS:
items = plan.get(array_name)
if not isinstance(items, list):
_issue(
issues,
code="plan_array_missing",
path=f"practice_plan.{array_name}",
message="array is required",
)
continue
if array_name in {"position_matrix", "dire_steps"} and not items:
_issue(
issues,
code="material_plan_section_empty",
path=f"practice_plan.{array_name}",
message="at least one source-backed proposal is required",
severity="blocker",
)
for index, item in enumerate(items):
path = f"practice_plan.{array_name}[{index}]"
if not isinstance(item, dict):
_issue(
issues,
code="plan_item_not_object",
path=path,
message="item must be an object",
)
continue
try:
item_id = safe_identifier(item.get("id"), field=f"{path}.id")
except ValueError as exc:
_issue(
issues,
code="invalid_plan_item_id",
path=f"{path}.id",
message=str(exc),
)
item_id = ""
if item_id in seen_item_ids:
_issue(
issues,
code="duplicate_plan_item_id",
path=f"{path}.id",
message=f"duplicate item id {item_id}",
)
if item_id:
seen_item_ids.add(item_id)
_nonempty_text(item.get("title"), path=f"{path}.title", issues=issues)
_nonempty_text(item.get("detail"), path=f"{path}.detail", issues=issues)
item_sources = item.get("source_ids")
if not isinstance(item_sources, list) or not item_sources:
_issue(
issues,
code="item_sources_missing",
path=f"{path}.source_ids",
message="every proposal must cite at least one source",
)
else:
unknown_sources = sorted(set(map(str, item_sources)) - all_source_ids)
if unknown_sources:
_issue(
issues,
code="unknown_source_reference",
path=f"{path}.source_ids",
message="unknown source ids: " + ", ".join(unknown_sources),
)
if len(item_sources) != len(set(map(str, item_sources))):
_issue(
issues,
code="duplicate_source_reference",
path=f"{path}.source_ids",
message="source ids must be unique",
)
item_fact_ids = item.get("case_fact_ids")
if not isinstance(item_fact_ids, list):
_issue(
issues,
code="case_fact_ids_missing",
path=f"{path}.case_fact_ids",
message="case fact dependency array is required",
)
else:
unknown_facts = sorted(set(map(str, item_fact_ids)) - fact_ids)
if unknown_facts:
_issue(
issues,
code="unknown_case_fact_reference",
path=f"{path}.case_fact_ids",
message="unknown case fact ids: " + ", ".join(unknown_facts),
)
review_status = item.get("review_status")
if review_status not in ITEM_REVIEW_STATUSES:
_issue(
issues,
code="invalid_item_review_status",
path=f"{path}.review_status",
message="unsupported review status",
)
else:
review_counts[str(review_status)] += 1
if review_status == "confirmed":
_validate_confirmation(
item.get("confirmation"),
path=f"{path}.confirmation",
issues=issues,
)
elif review_status in {"proposed", "blocked"}:
_issue(
issues,
code="plan_item_requires_review",
path=f"{path}.review_status",
message=f"{array_name} item {item_id or index} is {review_status}",
severity="blocker",
)
_nonempty_text(
plan.get("sari_question_draft"),
path="practice_plan.sari_question_draft",
issues=issues,
)
limitations = plan.get("limitations")
if not isinstance(limitations, list) or any(
not isinstance(item, str) or not item.strip() for item in limitations
):
_issue(
issues,
code="limitations_invalid",
path="practice_plan.limitations",
message="limitations must be a list of non-empty case-specific statements",
)
professional_review = plan.get("professional_review")
if not isinstance(professional_review, dict):
_issue(
issues,
code="professional_review_missing",
path="practice_plan.professional_review",
message="professional review object is required",
)
else:
status = professional_review.get("status")
if status not in {"pending", "reviewed", "rejected"}:
_issue(
issues,
code="invalid_professional_review_status",
path="practice_plan.professional_review.status",
message="unsupported status",
)
elif status != "reviewed":
_issue(
issues,
code="professional_review_pending",
path="practice_plan.professional_review.status",
message=f"professional review is {status}",
severity="blocker",
)
else:
reviewer_id = _nonempty_text(
professional_review.get("reviewer_id"),
path="practice_plan.professional_review.reviewer_id",
issues=issues,
max_length=120,
)
del reviewer_id
role = _nonempty_text(
professional_review.get("reviewer_role"),
path="practice_plan.professional_review.reviewer_role",
issues=issues,
max_length=120,
)
if role.casefold() in MODEL_ROLES or "professional" not in role.casefold():
_issue(
issues,
code="invalid_professional_reviewer_role",
path="practice_plan.professional_review.reviewer_role",
message="reviewed status requires a professional human reviewer role",
)
_validate_timestamp(
professional_review.get("reviewed_at"),
path="practice_plan.professional_review.reviewed_at",
issues=issues,
)
if review_counts["proposed"] or review_counts["blocked"]:
_issue(
issues,
code="reviewed_plan_contains_unresolved_items",
path="practice_plan.professional_review.status",
message="a reviewed plan cannot contain proposed or blocked items",
)
return review_counts
def _update_run_intake(
output_dir: Path,
*,
run_id: str,
validation_status: str,
bindings: dict[str, str | None],
) -> None:
path = output_dir / "run_intake.json"
if not path.exists():
return
payload = load_json_object(path)
if payload.get("run_id") != run_id or payload.get("plugin") != PLUGIN_NAME:
raise ValueError("run_intake.json belongs to another run")
trace = payload.get("execution_trace")
if not isinstance(trace, list):
trace = []
output_reference = (
"outputs"
if payload.get("path_reference") == "run_root_relative"
else output_dir.as_posix()
)
trace.append(
{
"step_id": f"validate_practice_case_{len(trace) + 1}",
"kind": "deterministic_validation",
"command": [
"python",
"scripts/validate_practice_case.py",
"--case-intake",
"case_intake_draft.json",
"--practice-plan",
"practice_plan_draft.json",
"--official-sources",
"official_sources.json",
"--output-dir",
output_reference,
],
"execution_location": "local_python",
"status": validation_status,
"inputs": [
"case_intake_draft.json",
"practice_plan_draft.json",
"official_sources.json",
],
"outputs": ["practice_validation_audit.json"],
"bindings": bindings,
}
)
payload["execution_trace"] = trace
payload["status"] = (
"ready_for_review"
if validation_status == "passed"
else "partial" if validation_status == "passed_with_blockers" else "blocked"
)
write_private_json(path, payload)
def validate_practice_case(
case_intake_path: Path,
practice_plan_path: Path,
official_sources_path: Path,
output_dir: Path,
*,
local_inventory_path: Path | None = None,
) -> dict[str, Any]:
"""Validate structure, provenance, bindings, and professional-review gates."""
output_dir = ensure_safe_output_dir(output_dir, plugin_root=PLUGIN_ROOT)
issues: list[dict[str, Any]] = []
intake = load_json_object(case_intake_path)
plan = load_json_object(practice_plan_path)
if plan.get("jurisdiction", "IT") != intake.get("jurisdiction", "IT"):
_issue(
issues,
code="jurisdiction_mismatch",
path="practice_plan.jurisdiction",
message="Plan jurisdiction must match intake independently of language",
)
manifest = load_json_object(official_sources_path)
_walk_prohibited_secrets(intake, path="case_intake", issues=issues)
_walk_prohibited_secrets(plan, path="practice_plan", issues=issues)
run_id, fact_ids = _validate_case_intake(intake, issues=issues)
source_ids = _validate_source_manifest(
manifest, run_id=run_id, output_dir=output_dir, issues=issues
)
review_counts = _validate_plan(
plan,
run_id=run_id,
fact_ids=fact_ids,
source_ids=source_ids,
issues=issues,
)
inventory_hash: str | None = None
if local_inventory_path is not None:
inventory = load_json_object(local_inventory_path)
if inventory.get("plugin") != PLUGIN_NAME or inventory.get("run_id") != run_id:
_issue(
issues,
code="local_inventory_binding_mismatch",
path="local_evidence_inventory",
message="local inventory plugin/run id must match the case",
)
inventory_hash = sha256_file(local_inventory_path)
ocr = inventory.get("ocr")
if isinstance(ocr, dict) and ocr.get("visual_confirmation_required"):
_issue(
issues,
code="ocr_visual_confirmation_required",
path="local_evidence_inventory.ocr",
message="OCR text remains partial until a human checks each source image",
severity="blocker",
)
errors = [issue for issue in issues if issue["severity"] == "error"]
blockers = [issue for issue in issues if issue["severity"] == "blocker"]
status = (
"schema_error" if errors else "passed_with_blockers" if blockers else "passed"
)
bindings = {
"case_intake_sha256": sha256_file(case_intake_path),
"practice_plan_sha256": sha256_file(practice_plan_path),
"official_sources_sha256": sha256_file(official_sources_path),
"local_evidence_inventory_sha256": inventory_hash,
}
audit = {
"schema_version": "1.0",
"plugin": PLUGIN_NAME,
"workflow": PLUGIN_NAME,
"run_id": run_id,
"status": status,
"semantic_decisions_performed": False,
"bindings": bindings,
"source_count": len(source_ids),
"review_counts": review_counts,
"error_count": len(errors),
"blocker_count": len(blockers),
"issues": issues,
}
audit_path = output_dir / "practice_validation_audit.json"
if not errors:
validated_intake_path = write_private_json(
output_dir / "case_intake_validated.json", intake
)
validated_plan_path = write_private_json(
output_dir / "practice_plan_validated.json", plan
)
audit["validated_bindings"] = {
"case_intake_validated_sha256": sha256_file(validated_intake_path),
"practice_plan_validated_sha256": sha256_file(validated_plan_path),
}
write_private_json(audit_path, audit)
_update_run_intake(
output_dir,
run_id=run_id,
validation_status=status,
bindings=bindings,
)
return audit
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--case-intake", type=Path, required=True)
parser.add_argument("--practice-plan", type=Path, required=True)
parser.add_argument("--official-sources", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--client-engagement", type=Path, required=True)
parser.add_argument("--local-inventory", type=Path)
args = parser.parse_args(argv)
try:
input_paths = [
args.case_intake,
args.practice_plan,
args.official_sources,
]
if args.local_inventory is not None:
input_paths.append(args.local_inventory)
context = load_running_case_context(
args.client_engagement,
input_paths=input_paths,
output_dir=args.output_dir,
)
for path in input_paths:
require_case_artifact_run(path, run_id=context["run_id"])
audit = validate_practice_case(
args.case_intake,
args.practice_plan,
args.official_sources,
args.output_dir,
local_inventory_path=args.local_inventory,
)
except (
AssuranceContractError,
OSError,
ValueError,
json.JSONDecodeError,
) as exc:
LOGGER.error("VALIDATION_BLOCKED: %s", exc)
return 2
LOGGER.info(
"Validation %s: %s errors, %s blockers",
audit["status"],
audit["error_count"],
audit["blocker_count"],
)
return 0 if audit["status"] != "schema_error" else 2
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(message)s")
raise SystemExit(main())
SHA-256: bce6a669b88a6502735be4b3bcd329392f8f5f72dfe2cfc618468623fe522848