
    `gjj                        d Z ddlmZ ddl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 g dZddlmZmZ e G d	 d
             ZdddZ ed      Z G d dee         Zy)u  Shared substrate for external secret-source backends.

Every backend (Bitwarden, 1Password, …) needs the same handful of
security-sensitive primitives:

  * a uniform result object (:class:`FetchResult`),
  * environment-variable name validation (:func:`is_valid_env_name`),
  * a two-layer fetch cache whose disk half writes atomically with ``0600``
    permissions and honours a TTL (:class:`DiskCache`, :class:`CachedFetch`).

These used to live inline inside ``bitwarden.py``.  Pulling them here means
the atomic-write / ``0600`` / TTL logic is audited and fixed in exactly one
place instead of drifting across copy-pasted per-backend modules — each
backend supplies only its own cache-key shape and a serializer for it.

Nothing in this module ever raises out to the caller's hot path: 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)FetchResultCachedFetch	DiskCacheis_valid_env_nameresolve_cache_home)r   r   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                V    |dk  ryt        j                          | j                  z
  |k  S )Nr   F)timer   )selfttl_secondss     N/root/.hermes/venv/lib/python3.12/site-packages/agent/secret_sources/_cache.pyis_freshzCachedFetch.is_fresh@   s'    !		doo-<<    N)r   r   returnbool)__name__
__module____qualname____doc____annotations__r    r   r   r   r   9   s    E=r   r   c                r    | 4t        t        j                  dt        j                         dz              } | S )a   Resolve the Hermes home used for cache paths.

    ``home_path`` is whatever ``load_hermes_dotenv()`` already resolved;
    falling back to ``$HERMES_HOME`` / ``~/.hermes`` keeps direct callers
    (and tests that don't thread a home through) working.
    HERMES_HOMEz.hermes)r   osgetenvhome)	home_paths    r   r   r   M   s0     =$))+	2IJK	r   Kc                  d    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 lives at ``<hermes_home>/cache/<basename>``::

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

    The file holds only secret *values* keyed by the serialized cache key —
    never raw auth material.  Backends are responsible for fingerprinting
    tokens/sessions *before* they reach ``key_serializer`` so the token can't
    land in the key.

    Writes are atomic (``mkstemp`` → ``chmod 0600`` → ``os.replace``) and the
    containing ``cache/`` directory is forced to ``0700`` — ``mkdir``'s mode is
    umask-subject, so the chmod is the reliable form.  Both ``read`` and
    ``write`` short-circuit when ``ttl_seconds <= 0``, so setting the TTL to
    zero disables *both* cache layers symmetrically: a user opting out never
    gets secret values written to disk at all.
    c               `    || _         || _        |j                  dd      d   }d| d| _        y )N.   r   _)	_basename_key_serializersplit_tmp_prefix)r   basenamekey_serializerstems       r   __init__zDiskCache.__init__p   s8    !- ~~c1%a(tfA;r   Nc                8    t        |      dz  | j                  z  S )Ncache)r   r/   r   r(   s     r   pathzDiskCache.pathx   s    !),w6GGr   c                   |dk  ry| j                  |      }	 t        |dd      5 }t        j                  |      }ddd       t        t              sy|j                  d      | j                  |      k7  r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        |      	      }|j                  |      sy|S # 1 sw Y   xY w# t        t        j
                  f$ r Y yw xY wc c}
}	w )
zReturn a fresh cached entry for ``key``, or None.

        Best-effort: any I/O or parse error, a key mismatch, or a stale entry
        all return None so the caller re-fetches.
        r   Nrutf-8encodingkeyr   r   )r   r   )r:   openjsonloadOSErrorJSONDecodeError
isinstancedictgetr0   intr   itemsstrr   r   )r   r@   r   r(   r:   fpayloadr   r   kvtypedentrys                r   readzDiskCache.read{   s;    !yy#	dC'2 'a))A,' '4(;;u!5!5c!::++i([[.
'4(
:U|0T %]]_!
Q
1c0BzRSUXGYAqD!
 !
 EeJ6GH~~k*)' '--. 		!
s9   D> D2D> E/E E2D;7D> >EEc                   |dk  ry| j                  |      }	 |j                  }|j                  dd       	 t        j                  |d       | j                  |      |j                  |j                  d}t        j                  | j                  dt        |            \  }}		 t        j                  |d	d
      5 }
t        j                  ||
       ddd       t        j                  |	d       t        j                   |	|       y# t
        $ r Y w xY w# 1 sw Y   ExY w# t"        $ r' 	 t        j$                  |	        # t
        $ r Y  w xY ww xY w# t
        $ r Y yw xY w)u   Persist ``entry`` for ``key`` atomically at mode ``0600``.

        No-op when ``ttl_seconds <= 0`` (so caching is genuinely off) or on any
        I/O error — the next invocation just re-fetches.
        r   NT)parentsexist_oki  )r@   r   r   z.tmp)prefixsuffixdirwr=   r>   i  )r:   parentmkdirr%   chmodrD   r0   r   r   tempfilemkstempr2   rK   fdopenrB   dumpreplaceBaseExceptionunlink)r   r@   rQ   r   r(   r:   	cache_dirrM   fdtmprL   s              r   writezDiskCache.write   sU    !yy#	IOOD4O8E* ++C0 ==#..G &&''C	NGB
YYr39 *QIIgq)*e$

3%!  * * ! IIcN     		s   E D AE 'D& ?D4D& 	DE DE D#D& &	E0EE	EEEEE 	E%$E%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)r:   rc   FileNotFoundErrorrD   r9   s     r   clearzDiskCache.clear   s3    	IIi '')!7+ 		s   " 44)r3   rK   r4   zCallable[[K], str]r   NoneNr(   Optional[Path]r   r   )r@   r)   r   r   r(   rn   r   zOptional[CachedFetch])
r@   r)   rQ   r   r   r   r(   rn   r   rk   )r(   rn   r   rk   )	r   r   r   r    r6   r:   rR   rg   rj   r"   r   r   r   r   \   s    &'H %)	## # "	#
 
#T %).. . 	.
 ". 
.`r   r   rl   rm   )r    
__future__r   rB   r%   r]   r   dataclassesr   pathlibr   typingr   r   r   r	   r
   __all__agent.secret_sources.baser   r   r   r   r)   r   r"   r   r   <module>ru      sn   ( #  	   !  = = 	= 	= 	=&	 CLy
 yr   