fix(report): bind professional report hash to the delivered packet (BUG-693, BUG-694)
Rebind after sanitize, write an explicit binding_scope, and make the quality gate recompute coverage. Wall-clock fields stay in the packet but out of the hash.
This commit is contained in:
@@ -9,6 +9,19 @@ import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.calculation_profile_contract import (
|
||||
ALLOWED_BINDING_EXCLUDED_PATHS,
|
||||
ALLOWED_BINDING_EXCLUDED_TOP_KEYS,
|
||||
hash_bound_result,
|
||||
)
|
||||
except ImportError: # pragma: no cover - direct-script execution
|
||||
from calculation_profile_contract import (
|
||||
ALLOWED_BINDING_EXCLUDED_PATHS,
|
||||
ALLOWED_BINDING_EXCLUDED_TOP_KEYS,
|
||||
hash_bound_result,
|
||||
)
|
||||
|
||||
|
||||
SCHEMA_VERSION = "jyotish.full_report_quality_gate.v1"
|
||||
REPORT_SCHEMA = "pl9_style_professional_export_v1"
|
||||
@@ -170,6 +183,45 @@ def evaluate_full_report(packet: dict[str, Any], rendered_markdown: str | None =
|
||||
else:
|
||||
checks.append(_check("provenance:result_binding", "passed", "input/result hashes match"))
|
||||
|
||||
scope = _as_dict(binding.get("binding_scope"))
|
||||
if "excluded_top_keys" not in scope or "excluded_paths" not in scope:
|
||||
blocking_reasons.append("provenance_binding_scope_absent")
|
||||
checks.append(_check(
|
||||
"provenance:result_binding_scope",
|
||||
"blocked",
|
||||
"provenance_binding_scope_absent",
|
||||
))
|
||||
else:
|
||||
extra_top = {
|
||||
str(item) for item in (scope.get("excluded_top_keys") or ())
|
||||
} - set(ALLOWED_BINDING_EXCLUDED_TOP_KEYS)
|
||||
extra_paths = {
|
||||
str(item) for item in (scope.get("excluded_paths") or ())
|
||||
} - set(ALLOWED_BINDING_EXCLUDED_PATHS)
|
||||
if extra_top or extra_paths:
|
||||
blocking_reasons.append("provenance_binding_scope_unexpected_exclusion")
|
||||
checks.append(_check(
|
||||
"provenance:result_binding_scope",
|
||||
"blocked",
|
||||
"provenance_binding_scope_unexpected_exclusion",
|
||||
))
|
||||
elif (
|
||||
isinstance(expected_input_hash, str)
|
||||
and hash_bound_result(packet, expected_input_hash, scope) != expected_result_hash
|
||||
):
|
||||
blocking_reasons.append("provenance_binding_scope_mismatch")
|
||||
checks.append(_check(
|
||||
"provenance:result_binding_scope",
|
||||
"blocked",
|
||||
"provenance_binding_scope_mismatch",
|
||||
))
|
||||
else:
|
||||
checks.append(_check(
|
||||
"provenance:result_binding_scope",
|
||||
"passed",
|
||||
"clipped payload matches result_hash",
|
||||
))
|
||||
|
||||
chart_identity = _as_dict(packet.get("chart_identity"))
|
||||
for key in (
|
||||
"chart_profile_id",
|
||||
|
||||
Reference in New Issue
Block a user