#!/usr/bin/env python3 from typing import Any from output_review_privacy import forbidden_decision_field_paths SCRIPT_INTERFACE = "internal-module" SCRIPT_INTERFACE_REASON = "Imported by world_class_evidence_contract.py to validate native client telemetry evidence from metadata event rows." ADOPTION_EVENTS = {"skill_activation", "skill_output"} def add_error(errors: list[str], condition: bool, message: str) -> None: if not condition: errors.append(message) def real_int(value: Any) -> int | None: return value if isinstance(value, int) and not isinstance(value, bool) else None def object_list(value: Any) -> list[dict[str, Any]]: return [item for item in value if isinstance(item, dict)] if isinstance(value, list) else [] def validate_no_raw_fields(payload: dict[str, Any], label: str, errors: list[str]) -> None: blocked = forbidden_decision_field_paths(payload, label) add_error( errors, not blocked, f"native-client-telemetry {label} must not include raw content, credential, secret, token, prompt, output, transcript, message, or answer-key fields: " + ", ".join(blocked[:8]), ) def source_types(payload: dict[str, Any]) -> dict[str, Any]: summary = payload.get("summary", {}) if isinstance(payload.get("summary"), dict) else {} value = summary.get("source_types", {}) return value if isinstance(value, dict) else {} def validate_adoption_report(adoption: dict[str, Any], errors: list[str]) -> None: summary = adoption.get("summary", {}) if isinstance(adoption.get("summary"), dict) else {} privacy = adoption.get("privacy_contract", {}) if isinstance(adoption.get("privacy_contract"), dict) else {} events = object_list(adoption.get("recent_events", [])) adoption_rows = object_list(adoption.get("adoption_by_skill", [])) external_summary_count = real_int(source_types(adoption).get("external")) adoption_sample_count = real_int(summary.get("adoption_sample_count")) validate_no_raw_fields(adoption, "adoption_drift_report", errors) add_error(errors, adoption.get("ok") is True, "native-client-telemetry adoption drift report ok must be true") add_error( errors, bool(external_summary_count and external_summary_count > 0), "native-client-telemetry adoption drift summary.source_types.external must be >0", ) add_error( errors, bool(adoption_sample_count and adoption_sample_count > 0), "native-client-telemetry adoption drift summary.adoption_sample_count must be >0", ) add_error( errors, privacy.get("raw_content_allowed") is False, "native-client-telemetry adoption drift privacy_contract.raw_content_allowed must be false", ) add_error( errors, privacy.get("raw_event_log_packaged") is False, "native-client-telemetry adoption drift privacy_contract.raw_event_log_packaged must be false", ) add_error(errors, bool(events), "native-client-telemetry adoption drift recent_events must not be empty") external_events = [item for item in events if item.get("source") == "external"] external_adoption_events = [item for item in external_events if item.get("event") in ADOPTION_EVENTS] add_error( errors, bool(external_summary_count and len(external_events) >= external_summary_count), "native-client-telemetry external event rows must cover summary.source_types.external", ) add_error( errors, bool(external_adoption_events), "native-client-telemetry must include at least one external adoption event row", ) add_error( errors, bool( adoption_sample_count and len([item for item in events if item.get("event") in ADOPTION_EVENTS]) >= adoption_sample_count ), "native-client-telemetry adoption event rows must cover summary.adoption_sample_count", ) add_error( errors, any((count := real_int(row.get("adoption_events"))) is not None and count > 0 for row in adoption_rows), "native-client-telemetry adoption_by_skill must include at least one adoption event", ) def validate_recipe_report(recipes: dict[str, Any], errors: list[str]) -> None: summary = recipes.get("summary", {}) if isinstance(recipes.get("summary"), dict) else {} privacy = recipes.get("privacy_contract", {}) if isinstance(recipes.get("privacy_contract"), dict) else {} recipe_rows = object_list(recipes.get("recipes", [])) recipe_count = real_int(summary.get("recipe_count")) metadata_count = real_int(summary.get("metadata_only_recipe_count")) validate_no_raw_fields(recipes, "telemetry_hook_recipes", errors) add_error(errors, recipes.get("ok") is True, "native-client-telemetry hook recipes report ok must be true") add_error( errors, bool(metadata_count and metadata_count > 0), "native-client-telemetry hook recipes summary.metadata_only_recipe_count must be >0", ) add_error( errors, privacy.get("raw_content_allowed") is False, "native-client-telemetry hook recipes privacy_contract.raw_content_allowed must be false", ) add_error(errors, bool(recipe_rows), "native-client-telemetry hook recipes rows must not be empty") if recipe_count is not None: add_error(errors, len(recipe_rows) == recipe_count, "native-client-telemetry hook recipes length must equal summary.recipe_count") if metadata_count is not None: add_error( errors, sum(1 for item in recipe_rows if item.get("metadata_only") is True) == metadata_count, "native-client-telemetry metadata_only recipe rows must match summary.metadata_only_recipe_count", ) add_error( errors, all(item.get("source") == "external" for item in recipe_rows), "native-client-telemetry hook recipe rows must use source external", ) add_error( errors, all(item.get("native_auto_capture") is False for item in recipe_rows), "native-client-telemetry hook recipe rows must not claim native_auto_capture", ) def validate_native_telemetry_report(adoption: dict[str, Any], recipes: dict[str, Any], errors: list[str]) -> None: if adoption: validate_adoption_report(adoption, errors) if recipes: validate_recipe_report(recipes, errors)