"""Independent syntax-only audit of explicit detection-core v1 readers.""" from __future__ import annotations import ast import subprocess from dataclasses import dataclass from pathlib import Path from core_migration_scope import CUTOVER_DELETIONS REPO = Path(__file__).resolve().parents[1] _EXCLUDED_PREFIXES = ("benchmarks/fixtures/", "benchmarks/pairs/", "tests/fixtures/") _LEGACY_IMPORTS: dict[str, frozenset[str] | None] = { "openultrasast.core": frozenset( { "AdmissionManifest", "AdmissionManifestError", "BoundedSilence", "Budget", "Complexity", "Coverage", "FactPack", "Guard", "Hop", "InstrumentFailure", "Judgment", "ProbeResult", "ProvenClaim", "PushRequest", "PushVerdict", "TraceRecord", "Witness", "admit_fact_pack", "as_clean", "check_reads", "consume_verdict", "validate_admission_manifest", } ), "openultrasast.core._legacy_factpack": None, "openultrasast.core.admission": frozenset( { "AdmissionManifest", "AdmissionManifestError", "REPORT_SCHEMA_KEYS", "validate_admission_manifest", "validate_report_schema", } ), "openultrasast.core.budget": frozenset({"Budget", "Complexity"}), "openultrasast.core.consumer": frozenset({"consume_verdict"}), "openultrasast.core.coverage": frozenset({"Coverage", "InstrumentFailure", "ResolutionOutcome", "check_reads"}), "openultrasast.core.factpack": frozenset({"FactPackError", "admit_fact_pack", "admit_pack_file", "load_pack_file", "verify_fact_pack"}), "openultrasast.core.probes": frozenset( { "FittedComponentBlocked", "LegacyProbeResult", "ShortcutDetected", "assert_no_shortcut", "require_admission_evidence", "reversed_pair_probe", } ), "openultrasast.core.provenance": frozenset({"ProvenanceMismatch", "stamp", "verify_stamp"}), "openultrasast.core.trace": None, "openultrasast.core.verdict": frozenset({"Judgment", "ProvenClaim", "as_clean"}), } _TOP_LEVEL_COMPATIBILITY = _LEGACY_IMPORTS["openultrasast.core"] or frozenset() class LegacyReaderAuditError(ValueError): """The explicit-reader audit could not read or parse its declared scope.""" @dataclass(frozen=True) class ReaderAuditReport: files_read: int bytes_read: int capabilities: tuple[tuple[str, str], ...] def tracked_python() -> tuple[Path, ...]: completed = subprocess.run(["git", "ls-files", "*.py"], cwd=REPO, check=True, capture_output=True, text=True) deleted = set( subprocess.run( ["git", "ls-files", "--deleted", "--", "*.py"], cwd=REPO, check=True, capture_output=True, text=True, ).stdout.splitlines() ) unexpected = deleted - CUTOVER_DELETIONS if unexpected: raise LegacyReaderAuditError(f"unexpected tracked Python deletion: {min(unexpected)}") return tuple( REPO / relative for relative in completed.stdout.splitlines() if relative.endswith(".py") and relative not in deleted and not relative.startswith(_EXCLUDED_PREFIXES) ) def _dotted(node: ast.AST) -> str: if isinstance(node, ast.Name): return node.id if isinstance(node, ast.Attribute): prefix = _dotted(node.value) return f"{prefix}.{node.attr}" if prefix else "" return "" def _explicit_imports(tree: ast.Module) -> set[str]: anchors = set() for node in ast.walk(tree): if isinstance(node, ast.ImportFrom) and node.module in _LEGACY_IMPORTS: allowed = _LEGACY_IMPORTS[node.module] anchors.update(f"import_from:{node.module}:{alias.name}" for alias in node.names if allowed is None or alias.name in allowed) elif isinstance(node, ast.Import): anchors.update( f"import:{alias.name}" for alias in node.names if alias.name in _LEGACY_IMPORTS and _LEGACY_IMPORTS[alias.name] is None ) return anchors def _direct_submodule_bindings(tree: ast.Module) -> dict[str, str]: bindings = {} for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: if alias.name in _LEGACY_IMPORTS and alias.name != "openultrasast.core": bindings[alias.asname or alias.name] = alias.name elif isinstance(node, ast.ImportFrom) and node.module == "openultrasast.core": for alias in node.names: module = f"openultrasast.core.{alias.name}" if module in _LEGACY_IMPORTS: bindings[alias.asname or alias.name] = module return bindings def _module_attribute_reads(tree: ast.Module) -> set[str]: bindings = _direct_submodule_bindings(tree) reads = set() for node in ast.walk(tree): if not isinstance(node, ast.Attribute): continue module = bindings.get(_dotted(node.value)) if module is None: continue allowed = _LEGACY_IMPORTS[module] if allowed is None or node.attr in allowed: reads.add(f"module_attr:{module}:{node.attr}") return reads def _top_level_aliases(tree: ast.Module) -> set[str]: aliases = set() for node in ast.walk(tree): if isinstance(node, ast.Import): aliases.update(alias.asname for alias in node.names if alias.name == "openultrasast.core" and alias.asname) elif isinstance(node, ast.ImportFrom) and node.module == "openultrasast": aliases.update(alias.asname or alias.name for alias in node.names if alias.name == "core") return aliases def _top_level_reads(tree: ast.Module) -> set[str]: aliases = _top_level_aliases(tree) reads = set() for node in ast.walk(tree): if not isinstance(node, ast.Attribute) or node.attr not in _TOP_LEVEL_COMPATIBILITY: continue base = _dotted(node.value) if base in aliases or base == "openultrasast.core": reads.add(f"top_level:{node.attr}") return reads def audit_legacy_readers(paths: tuple[Path, ...]) -> ReaderAuditReport: capabilities = set() files_read = 0 bytes_read = 0 for path in paths: relative = path.relative_to(REPO).as_posix() try: content = path.read_bytes() except OSError as exc: raise LegacyReaderAuditError(f"reader audit input is not readable: {relative}") from exc if not content: raise LegacyReaderAuditError(f"reader audit input must contain nonzero bytes: {relative}") try: tree = ast.parse(content, filename=relative) except (SyntaxError, UnicodeDecodeError) as exc: raise LegacyReaderAuditError(f"reader audit input is not parseable: {relative}") from exc files_read += 1 bytes_read += len(content) anchors = _explicit_imports(tree) | _module_attribute_reads(tree) | _top_level_reads(tree) capabilities.update((relative, anchor) for anchor in anchors) return ReaderAuditReport(files_read, bytes_read, tuple(sorted(capabilities))) __all__ = ["LegacyReaderAuditError", "ReaderAuditReport", "audit_legacy_readers", "tracked_python"]