jeevesagent.data.lineage
========================

.. py:module:: jeevesagent.data.lineage

.. autoapi-nested-parse::

   Freshness and lineage policies for :class:`CertifiedValue`.

   A :class:`CertifiedValue` (defined in :mod:`jeevesagent.core.types`)
   carries provenance metadata: ``source``, ``fetched_at``, optional
   ``valid_until``, ``schema_version``, and a tuple of upstream value IDs
   in ``lineage``.

   This module supplies the policy *types* and *helpers*. Two flavours:

   * :class:`FreshnessPolicy` — declare a maximum age per source
     prefix; ``valid_until`` always wins when set on the value itself.
   * :class:`LineagePolicy` — declare an allow-list of source prefixes
     every value in a lineage chain must originate from.

   Two helper styles for each:

   * ``check_*`` returns ``True``/``False`` so callers can branch.
   * ``require_*`` raises the appropriate
     :class:`~jeevesagent.core.errors.FreshnessError` /
     :class:`~jeevesagent.core.errors.LineageError` so callers can rely
     on exception propagation.



Classes
-------

.. autoapisummary::

   jeevesagent.data.lineage.FreshnessPolicy
   jeevesagent.data.lineage.LineagePolicy


Functions
---------

.. autoapisummary::

   jeevesagent.data.lineage.check_freshness
   jeevesagent.data.lineage.check_lineage
   jeevesagent.data.lineage.require_freshness
   jeevesagent.data.lineage.require_lineage


Module Contents
---------------

.. py:class:: FreshnessPolicy

   Maximum age for certified values from each source.

   ``per_source`` maps a source-prefix (matched with ``startswith``)
   to a ``timedelta``. The first prefix that matches wins. ``default``
   is used when no prefix matches; if also ``None``, the policy
   treats all values as fresh.


   .. py:method:: from_dict(per_source: dict[str, datetime.timedelta] | None = None, *, default: datetime.timedelta | None = None) -> FreshnessPolicy
      :classmethod:



   .. py:method:: max_age_for(source: str) -> datetime.timedelta | None


   .. py:attribute:: default
      :type:  datetime.timedelta | None
      :value: None



   .. py:attribute:: per_source
      :type:  tuple[tuple[str, datetime.timedelta], Ellipsis]
      :value: ()



.. py:class:: LineagePolicy

   Allow-list of source prefixes for the entire lineage chain.

   A :class:`CertifiedValue` is acceptable if every entry in
   ``value.lineage`` (interpreted as a source prefix) starts with one
   of the allowed prefixes.


   .. py:method:: from_iter(sources: list[str] | tuple[str, Ellipsis]) -> LineagePolicy
      :classmethod:



   .. py:attribute:: allowed_sources
      :type:  frozenset[str]


.. py:function:: check_freshness(value: jeevesagent.core.types.CertifiedValue, policy: FreshnessPolicy, *, now: datetime.datetime | None = None) -> bool

   Return ``True`` if ``value`` satisfies ``policy`` at ``now``.

   Logic:

   1. If ``valid_until`` is set on the value, fail if ``now > valid_until``.
   2. Look up ``policy.max_age_for(source)``. If ``None`` (no rule),
      the value is fresh by default.
   3. Otherwise fail if ``now - fetched_at > max_age``.


.. py:function:: check_lineage(value: jeevesagent.core.types.CertifiedValue, policy: LineagePolicy) -> bool

   Return ``True`` if every lineage source is allowed.

   The value's own ``source`` is also required to be in the allow-list
   — there's no point trusting a chain whose tip you don't.


.. py:function:: require_freshness(value: jeevesagent.core.types.CertifiedValue, policy: FreshnessPolicy, *, now: datetime.datetime | None = None) -> None

   Raise :class:`FreshnessError` when :func:`check_freshness` fails.


.. py:function:: require_lineage(value: jeevesagent.core.types.CertifiedValue, policy: LineagePolicy) -> None

   Raise :class:`LineageError` when :func:`check_lineage` fails.


