"""ReliabilityStep — writes provenance_verified + text_agreement (spec §6). Runs after :class:`~ix.pipeline.genai_step.GenAIStep`. Skips entirely when provenance is off OR when no provenance data was built (OCR-skipped text-only request, for example). Otherwise delegates to :func:`~ix.provenance.apply_reliability_flags`, which mutates each :class:`~ix.contracts.FieldProvenance` in place and fills the two summary counters (``verified_fields``, ``text_agreement_fields``) on ``quality_metrics``. No own dispatch logic — everything interesting lives in the normalisers + verifier modules and is unit-tested there. """ from __future__ import annotations from typing import cast from pydantic import BaseModel from ix.contracts import RequestIX, ResponseIX from ix.pipeline.step import Step from ix.provenance import apply_reliability_flags class ReliabilityStep(Step): """Fills per-field reliability flags on ``response.provenance``.""" async def validate(self, request_ix: RequestIX, response_ix: ResponseIX) -> bool: if not request_ix.options.provenance.include_provenance: return False return response_ix.provenance is not None async def process( self, request_ix: RequestIX, response_ix: ResponseIX ) -> ResponseIX: assert response_ix.provenance is not None # validate() guarantees ctx = response_ix.context texts: list[str] = ( list(getattr(ctx, "texts", []) or []) if ctx is not None else [] ) use_case_response_cls = cast( type[BaseModel], getattr(ctx, "use_case_response", None) if ctx is not None else None, ) apply_reliability_flags( provenance_data=response_ix.provenance, use_case_response=use_case_response_cls, texts=texts, ) return response_ix __all__ = ["ReliabilityStep"]