Ë
    áß²jà  ã                  ó  — d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 ddl
mZmZmZmZmZ ddlmZ ddlmZ g d	¢Zdd
„Ze G d„ d«      «       Zddd„Zdd„Zdd„Z ed«      Z G d„ dee   «      Z G d„ dee   «      ZdddœZd„ Zy)a©  Shared cache substrate for external secret-source backends.

Two-layer fetch cache (in-process + on-disk); the disk half writes atomically
with ``0600`` permissions and honours a TTL, so that logic is audited in exactly
one place. Each backend supplies only its cache-key shape and a serializer.
The disk layer is strictly best-effort: a miss just triggers a refetch, because
a cache problem must never block Hermes startup.
é    )ÚannotationsN)Ú	dataclass)ÚPath)ÚCallableÚDictÚGenericÚOptionalÚTypeVar)Úsecure_parent_dir)Úatomic_json_write)ÚCachedFetchÚ	DiskCacheÚSecretCacheÚatomic_write_jsonÚentry_from_payloadÚfingerprintÚresolve_cache_homec                ól   — t        j                  | j                  d«      «      j                  «       dd S )uE   SHA-256 prefix used as a cache key â€” never logged, never displayed.úutf-8Né   )ÚhashlibÚsha256ÚencodeÚ	hexdigest)Úmaterials    ú@/home/ubuntu/.hermes/hermes-agent/agent/secret_sources/_cache.pyr   r   !   s*   € ä�>‰>˜(Ÿ/™/¨'Ó2Ó3×=Ñ=Ó?ÀÀÐDÐDó    c                  ó.   — e Zd ZU dZded<   ded<   dd„Zy)	r   z;A set of fetched secret values plus when they were fetched.zDict[str, str]ÚsecretsÚfloatÚ
fetched_atc                óX   — |dkD  xr$ t        j                   «       | j                  z
  |k  S )Nr   )Útimer!   )ÚselfÚttl_secondss     r   Úis_freshzCachedFetch.is_fresh-   s%   € Ø˜Q‰ÒP¤D§I¡I£K°$·/±/Ñ$AÀ[Ñ#PÐPr   N)r%   r    ÚreturnÚbool)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__annotations__r&   © r   r   r   r   &   s   … áEàÓØÓôQr   r   c                ó$   — | €ddl m}  |«       } | S )z[``home_path`` as resolved by ``load_hermes_dotenv()``, else ``$HERMES_HOME``/``~/.hermes``.r   )Úget_hermes_home)Úhermes_constantsr0   )Ú	home_pathr0   s     r   r   r   1   s   € àÐÝ4á#Ó%ˆ	ØÐr   c                óx  — t        | t        «      sy| j                  d«      | j                  d«      }}t        |t        «      rt        |t        t        f«      sy|j                  «       D ��ci c]*  \  }}t        |t        «      sŒt        |t        «      sŒ(||“Œ, }}}t        |t	        |«      ¬«      S c c}}w )u±   ``{"secrets": {...}, "fetched_at": n}`` â†’ :class:`CachedFetch`, or None if malformed.

    Only strâ†’str pairs survive (JSON permits other types; env vars need strings).
    Nr   r!   )r   r!   )Ú
isinstanceÚdictÚgetÚintr    ÚitemsÚstrr   )Úpayloadr   r!   ÚkÚvÚtypeds         r   r   r   :   s“   € ô
 �gœtÔ$ØØ!Ÿ+™+ iÓ0°'·+±+¸lÓ2KˆZ€GÜ�gœtÔ$¬J°zÄCÌÀ<Ô,PØØ%Ÿm™m›o×[‘d�a˜´¸A¼sÕ1CÌ
ÐSTÔVYÕHZˆQ�‰TÐ[€EÑ[Ü˜u´°zÓ1BÔCÐCùó \s   Á.B6ÂB6ÂB6c                óh   — ddl m}  || j                  «       t        | «       t	        | |dd¬«       y)zðSecret cache entry at 0600 from creation; the containing dir is tightened to 0700
    (``secure_parent_dir`` refuses ``/``, top-level dirs and the install tree). Raises ``OSError``
    on failure; callers decide whether that is best-effort.r   )Úmkdir_under_hermes_homeNi€  )ÚindentÚmode)r1   r?   Úparentr   r   )Úpathr:   r?   s      r   r   r   H   s)   € õ 9á˜DŸK™KÔ(Ü�dÔÜ�d˜G¨D°uÖ=r   ÚKc                  ó@   — e Zd ZdZdd„Zd	d
d„Zd	dd„Zd	dd„Zd	dd„Zy)r   u?  Best-effort, profile-aware on-disk cache for fetched secret values.

    One JSON object per backend at ``<hermes_home>/cache/<basename>``::

        {"key": "<serialized cache key>", "secrets": {...}, "fetched_at": 1.0}

    The file holds only secret *values*, never raw auth material â€” backends
    fingerprint tokens/sessions before they reach ``key_serializer``. Both
    ``read`` and ``write`` short-circuit when ``ttl_seconds <= 0``, so a TTL of
    zero disables both layers symmetrically: an opted-out user never gets
    secret values written to disk at all.
    c               ó    — || _         || _        y ©N)Ú	_basenameÚ_key_serializer©r$   ÚbasenameÚkey_serializers      r   Ú__init__zDiskCache.__init__d   s   € Ø!ˆŒØ-ˆÕr   Nc                ó8   — t        |«      dz  | j                  z  S )NÚcache)r   rH   ©r$   r2   s     r   rC   zDiskCache.pathh   s   € Ü! )Ó,¨wÑ6¸¿¹ÑGÐGr   c                óˆ  — |dk  ry	 t        | j                  |«      dd¬«      5 }t        j                  |«      }ddd«       t        t        «      r#|j                  d«      | j                  |«      k7  ryt        |«      }|�|j                  |«      r|S dS # 1 sw Y   Œ_xY w# t        t        j
                  f$ r Y yw xY w)zEFresh cached entry for ``key``, or None (I/O error, mismatch, stale).r   NÚrr   )ÚencodingÚkey)ÚopenrC   ÚjsonÚloadÚOSErrorÚJSONDecodeErrorr4   r5   r6   rI   r   r&   )r$   rT   r%   r2   Úfr:   Úentrys          r   ÚreadzDiskCache.readk   s·   € à˜!ÒØð	Ü�d—i‘i 	Ó*¨C¸'ÔBð 'ÀaÜŸ)™) A›,�÷'ô ˜'¤4Ô(¨G¯K©K¸Ó,>À$×BVÑBVÐWZÓB[Ò,[ØÜ" 7Ó+ˆØÐ)¨e¯n©n¸[Ô.IˆuÐSÈtÐS÷'ð 'ûäœ×-Ñ-Ð.ò 	Ùð	ús'   ˆB% ¥B»B% ÂB"ÂB% Â%CÃ Cc                ó¸   — |dk  ry| j                  |«      |j                  |j                  dœ}	 t        | j	                  |«      |«       y# t
        $ r Y yw xY w)z[Persist ``entry`` atomically at mode 0600; no-op when ``ttl_seconds <= 0`` or on I/O error.r   N)rT   r   r!   )rI   r   r!   r   rC   rX   )r$   rT   r[   r%   r2   r:   s         r   ÚwritezDiskCache.writey   sY   € à˜!ÒØØ×.Ñ.¨sÓ3ÀÇÁÐ]b×]mÑ]mÑnˆð	Ü˜dŸi™i¨	Ó2°GÕ<øÜò 	Ùð	ús   ±A Á	AÁAc                ón   — 	 | j                  |«      j                  «        y# t        t        f$ r Y yw xY w)z6Delete the on-disk cache file if present (idempotent).N)rC   ÚunlinkÚFileNotFoundErrorrX   rP   s     r   ÚclearzDiskCache.clearƒ   s3   € ð	Ø�I‰I�iÓ ×'Ñ'Õ)øÜ!¤7Ð+ò 	Ùð	ús   ‚" ¢4³4©rK   r9   rL   zCallable[[K], str]r'   ÚNonerG   ©r2   úOptional[Path]r'   r   )rT   rD   r%   r    r2   rf   r'   úOptional[CachedFetch]©
rT   rD   r[   r   r%   r    r2   rf   r'   rd   ©r2   rf   r'   rd   )	r)   r*   r+   r,   rM   rC   r\   r^   rb   r.   r   r   r   r   V   s"   „ ñó.ôHôTôõr   r   c                  ó@   — e Zd ZdZdd„Z	 	 d	 	 	 d	d„Zd
dd„Zd
dd„Zy)r   zÜTwo-layer cache: in-process dict (L1) over a :class:`DiskCache` (L2).

    L1 saves repeated fetches WITHIN one process (CLI startup, gateway
    hot-reload); L2 saves them ACROSS back-to-back short-lived processes.
    c               ó6   — i | _         t        ||¬«      | _        y )N)rL   )Úmemoryr   ÚdiskrJ   s      r   rM   zSecretCache.__init__’   s   € Ø,.ˆŒÜ"+¨HÀ^Ô"Tˆ�	r   Nc                óÔ   — | j                   j                  |«      }|r|j                  |«      r|S |r |«       n| j                  j	                  |||«      }|�|| j                   |<   |S )zœFresh entry from L1, else from L2 (promoted into L1), else None.

        ``read_disk`` swaps in an alternative L2 reader (e.g. an encrypted file).
        )rl   r6   r&   rm   r\   )r$   rT   r%   r2   Ú	read_diskÚcachedÚdisk_cacheds          r   ÚlookupzSecretCache.lookup–   s`   € ð —‘—‘ Ó%ˆÙ�f—o‘o kÔ2ØˆMÙ%.‘i”k°D·I±I·N±NÀ3ÈÐU^Ó4_ˆØÐ"Ø*ˆD�K‰K˜ÑØÐr   c                ó^   — || j                   |<   | j                  j                  ||||«       y rG   )rl   rm   r^   )r$   rT   r[   r%   r2   s        r   ÚstorezSecretCache.store¤   s&   € Ø ˆ�‰�CÑØ�	‰	�‰˜˜U K°Õ;r   c                ón   — | j                   j                  «        | j                  j                  |«       y rG   )rl   rb   rm   rP   s     r   rb   zSecretCache.clear¨   s"   € Ø�‰×ÑÔØ�	‰	�‰˜	Õ"r   rc   )NN)
rT   rD   r%   r    r2   rf   ro   z-Optional[Callable[[], Optional[CachedFetch]]]r'   rg   rG   rh   ri   )r)   r*   r+   r,   rM   rr   rt   rb   r.   r   r   r   r   ‹   s6   „ ñóUð NRØJNðØGðØShóô<õ#r   r   )úagent.secret_sources.baseÚFetchResult)rv   Úis_valid_env_name)rw   rx   c                óÌ   — t         j                  | «      }|€t        dt        ›d| ›�«      ‚dd l}ddlm}  |t        | g|¢­Ž  t        |j                  |d   «      |d   «      S )Nzmodule z has no attribute r   )Ú	warn_onceé   )	Ú_PLUGIN_COMPAT_LAZYr6   ÚAttributeErrorr)   Ú	importlibÚhermes_cli.plugin_compatrz   ÚgetattrÚimport_module)ÚnameÚtargetr~   rz   s       r   Ú__getattr__r„   ¹   se   € Ü ×$Ñ$ TÓ*€FØ€~Ü˜w¤x lÐ2DÀTÀHÐMÓNÐNÛÝ2ÙŒh˜Ð&˜vÓ&Ü�9×*Ñ*¨6°!©9Ó5°v¸a±yÓAÐAr   )r   r9   r'   r9   rG   re   )r:   Úobjectr'   rg   )rC   r   r:   r5   r'   rd   )r,   Ú
__future__r   r   rV   r#   Údataclassesr   Úpathlibr   Útypingr   r   r   r	   r
   r1   r   Úutilsr   Ú__all__r   r   r   r   r   rD   r   r   r|   r„   r.   r   r   ú<module>rŒ      s§   ðñõ #ã Û Û Ý !Ý ß =Õ =å .Ý #ò€óEð
 ÷Qð Qó ðQôóDó>ñ ˆCƒL€ô2�˜‘
ô 2ôj#�'˜!‘*ô #ðR @ØKñÐ óBr   