
    .cjLD                       U d Z ddlmZ ddlZddlZddlZddlmZm	Z	 ddl
mZ ddlmZmZmZ ddlmZmZmZmZmZ  ej        e          Zi Zded	<   d
ae G d d                      Ze G d d                      Ze G d d                      Zd
dd3dZd4dZ d5dZ!d6dZ"d6d Z#d7d&Z$d8d(Z%d9d*Z&d+Z'd:d/Z(	 d;d<d2Z)dS )=u  Secret-source registry + apply orchestrator.

This module owns everything that must be uniform across secret backends
so no individual source can get it wrong:

* registration (name/scheme uniqueness, API-version gating)
* per-source wall-clock timeout enforcement around ``fetch()``
* precedence: mapped sources beat bulk sources; within a shape,
  ``secrets.sources`` order (or registration order) decides; first
  claim wins — later sources never silently clobber an earlier one
* ``override_existing`` semantics (may beat .env/shell, never another
  secret source, never a protected var)
* cross-source conflict warnings (shadowed claims are always surfaced)
* provenance: which source supplied every applied var

The single entry point for startup is :func:`apply_all`, called from
``hermes_cli.env_loader._apply_external_secret_sources()``.

Plugins register additional sources via
``PluginContext.register_secret_source()`` which lands in
:func:`register_source`.  In-tree sources are registered lazily by
:func:`_ensure_builtin_sources` — the set of bundled sources is
deliberately closed (Bitwarden, and 1Password once it lands); new
third-party backends ship as standalone plugin repos implementing
:class:`agent.secret_sources.base.SecretSource`.
    )annotationsN)	dataclassfield)Path)DictListOptional)SECRET_SOURCE_API_VERSION	ErrorKindFetchResultSecretSourceis_valid_env_namezDict[str, SecretSource]_SOURCESFc                  <    e Zd ZU dZded<   ded<   ded<   ded<   dS )	
AppliedVarz7Provenance record for one env var the orchestrator set.strnamesourceshapebooloverrode_envN)__name__
__module____qualname____doc____annotations__     ?/home/ice/.hermes/hermes-agent/agent/secret_sources/registry.pyr   r   5   s?         AAIIIKKKJJJr   r   c                      e Zd ZU dZded<   ded<   ded<    ee          Zded	<    ee          Zded
<    ee          Z	ded<    ee          Z
ded<    ee          Zded<   dS )SourceReportz4One source's outcome within an :class:`ApplyReport`.r   r   labelr   resultdefault_factory	List[str]appliedskipped_existingskipped_claimedskipped_protectedskipped_invalidN)r   r   r   r   r   r   listr'   r(   r)   r*   r+   r   r   r   r!   r!   ?   s         >>IIIJJJt444G4444"'%"="="=====!&t!<!<!<O<<<<#(5#>#>#>>>>>!&t!<!<!<O<<<<<<r   r!   c                      e Zd ZU dZ ee          Zded<    ee          Z	ded<    ee          Z
ded<   edd            ZdS )ApplyReportz.Merged outcome of one orchestrated apply pass.r$   zList[SourceReport]sourceszDict[str, AppliedVar]
provenancer&   	conflictsreturnr   c                *    t          | j                  S N)r   r0   )selfs    r   applied_anyzApplyReport.applied_anyU   s    DO$$$r   N)r2   r   )r   r   r   r   r   r,   r/   r   dictr0   r1   propertyr6   r   r   r   r.   r.   M   s         88"'%"="="=G====(-d(C(C(CJCCCC 5666I6666% % % X% % %r   r.   )replacer   r   r9   r   r2   c          	        t          | t                    st                              d|            dS t	          | dd          pd}|r@|                    dd                                          r||                                k    rt                              d|           dS t	          | dd          t          k    r3t                              d	|t	          | dd
          t                     dS t	          | dd          dvr-t                              d|t	          | dd                     dS |t          v r|st                              d|           dS t	          | dd          }|rZt          
                                D ]@\  }}||k    r5t	          |dd          |k    r t                              d|||            dS A| t          |<   dS )ub  Register a secret source.  Returns True on success.

    Rejections are logged, never raised — a bad plugin must not take
    down startup.  ``replace`` allows tests / user plugins to override
    a bundled source of the same name (last-writer-wins like model
    providers), but scheme collisions across *different* names are
    always rejected.
    z=Ignoring secret source %r: does not inherit from SecretSourceFr    _z+Ignoring secret source with invalid name %rapi_versionNzXIgnoring secret source '%s': built against secret-source API v%s, this Hermes speaks v%s?r   )mappedbulkzEIgnoring secret source '%s': shape must be 'mapped' or 'bulk', got %rz9Secret source '%s' already registered; ignoring duplicateschemezKIgnoring secret source '%s': scheme '%s://' is already owned by source '%s'T)
isinstancer   loggerwarninggetattrr9   isalnumlowerr
   r   items)r   r9   r   rA   
other_nameothers         r   register_sourcerK   _   s    fl++ K	
 	
 	
 u662&&,"D t||C,,4466 $$**,,:N:NDdKKKuv}d++/HHH%'&-557P	
 	
 	

 uvw%%-???S'&'400	
 	
 	
 uxRTXYYYuVXt,,F !)!1!1 	 	JT!!geXt&D&D&N&N+&*  
 uuHTN4r   r   r   Optional[SecretSource]c                R    t                       t                              |           S r4   )_ensure_builtin_sourcesr   get)r   s    r   
get_sourcerP      s     <<r   List[SecretSource]c                 j    t                       t          t                                                    S r4   )rN   r,   r   valuesr   r   r   list_sourcesrT      s&    !!"""r   Nonec                    t           rdS da 	 ddlm}  t           |                        n,# t          $ r t
                              dd           Y nw xY w	 ddlm} t           |                       n,# t          $ r t
                              dd           Y nw xY w	 dd	l	m
} t           |                       dS # t          $ r  t
                              d
d           Y dS w xY w)zIdempotently register the bundled sources.

    Lazy so importing this module stays cheap and so a broken bundled
    source can never break registration of the others.
    NTr   )BitwardenSourcez2Failed to register bundled Bitwarden secret sourceexc_info)OnePasswordSourcez2Failed to register bundled 1Password secret source)CommandSourcez0Failed to register bundled command secret source)_BUILTINS_LOADEDagent.secret_sources.bitwardenrW   rK   	ExceptionrC   rD    agent.secret_sources.onepasswordrZ   agent.secret_sources.commandr[   )rW   rZ   r[   s      r   rN   rN      s     &BBBBBB))**** & & &K $ 	 	& 	& 	& 	& 	&&&FFFFFF))++,,,, & & &K $ 	 	& 	& 	& 	& 	&&&>>>>>>((((( & & &I $ 	 	& 	& 	& 	& 	& 	&&s3   + &AAA6 6&BB#C &C,+C,c                 <    t                                            dad S )NF)r   clearr\   r   r   r   _reset_registry_for_testsrc      s    NNr   cfgr7   	home_pathr   r   c                   |                      |          }t          j                            dd| j                   }	 |                    | j        ||          }	 |                    |          }n# t          j        j        $ rb |	                                 t                      }d|dd| j         d|_        t          j        |_        |cY |                    d	
           S t           $ r`}t                      }dt#          |          j         d| |_        t          j        |_        |cY d}~|                    d	
           S d}~ww xY w	 |                    d	
           n# |                    d	
           w xY wt)          |t                    s>t                      }dt#          |          j         d|_        t          j        |_        |S |S )u  Run source.fetch() under a wall-clock budget; never raises.

    The budget is enforced with a daemon worker thread: a source that
    blows its budget is reported as ``TIMEOUT`` and its (eventual)
    result is discarded.  The thread itself may linger until process
    exit — acceptable for a startup-only path, and strictly better than
    an unbounded hang on every ``hermes`` invocation.
       zsecret-src-)max_workersthread_name_prefix)timeoutzfetch exceeded z.0fuB   s budget — startup continued without this source (raise secrets.z-.timeout_seconds if the backend is just slow)F)waitzfetch raised z: Nzfetch returned z instead of FetchResult)fetch_timeout_seconds
concurrentfuturesThreadPoolExecutorr   submitfetchr#   TimeoutErrorcancelr   errorr   TIMEOUT
error_kindshutdownr^   typer   INTERNALrB   )	r   rd   re   rj   executorfuturer#   resexcs	            r   _fetch_with_timeoutr~      s    **3//G!44*E*E*E 5  H&sI>>	]]7]33FF!. 		 		 		MMOOO--CN'N N N;N N N I
 '.CNJJ 	u%%%%  	 	 	--CCS		(:CCcCCCI&/CNJJJJJu%%%%	 " 	u%%%%u%%%%fk** mmLd6ll3LLL 		 #+
MsI    E- A4 3E- 4AEE- )	E2?E1E2E- EE- -Fsecrets_cfgc                   t                       |                     d          }g }t          |t                    r|D ]9}t          |t                    r"|t
          v r||vr|                    |           :d |D             }|rIt                              dd	                    |          d	                    t
                    pd           t
          D ]}||vr|                    |           g }|D ]}t
          |         }|                     |          }t          |t                    r|ni }	 |                    |          r|                    |           i# t          $ r  t                              d|d           Y w xY w|S )	aE  Resolve which sources run, in which order.

    Order: the optional ``secrets.sources`` list wins; sources not named
    there follow in registration order.  Enabled = the source's own
    ``is_enabled`` says so for its config section.  Mapped-vs-bulk
    precedence is applied on top of this order by :func:`apply_all`.
    r/   c                N    g | ]"}t          |t                    r|t          v |#S r   )rB   r   r   ).0es     r   
<listcomp>z,_ordered_enabled_sources.<locals>.<listcomp>  sB     @ @ @ C((@-.h->-> ->->->r   z7secrets.sources names unknown source(s): %s (known: %s)z, nonez0Secret source '%s' is_enabled() raised; skippingTrX   )rN   rO   rB   r,   r   r   appendrC   rD   joinr7   
is_enabledr^   )	r   explicitorderentryunknownr   enabledr   rd   s	            r   _ordered_enabled_sourcesr      s    y))HE(D!! 
 	$ 	$E%%% $%8*;*;U@R@RU###@ @h @ @ @ 	NNI		'""DIIh$7$7$A6     uLL"$G 	0 	0$ood##T**2cc	0  %% 'v&&& 	0 	0 	0NNM$  0 0 0 0 0	0 Ns   3*E'FFOptional[Path]c                    | -t          |           }|j        j        dk    r|j        r|j        S dD ]@}t          j                            |d                                          }|r
|dk    r|c S AdS )zBest-effort active profile name for profile-scoped secret aliases.

    A named profile's HERMES_HOME is ``~/.hermes/profiles/<name>``; the
    default profile (``~/.hermes``) returns "".
    Nprofiles)HERMES_PROFILE_NAMEHERMES_PROFILEr;   default)r   parentr   osenvironrO   strip)re   resolvedenv_namevalues       r   _active_profile_namer     s     	???:--(--= =  
x,,2244 	Ui''LLL2r   )_API_KEY_TOKEN_SECRET_KEY	_PASSWORDvarprofileOptional[str]c                .   |sdS d|                     dd                                          z   }|                     |          sdS | dt          |                    rt	                    sdS t          fdt          D                       sdS S )zBMap ``FOO_<PROFILE>`` to ``FOO`` for the active profile when safe.Nr<   -c              3  B   K   | ]}                     |          V  d S r4   )endswith)r   saliass     r   	<genexpr>z(_profile_alias_target.<locals>.<genexpr>>  s/      ::Qu~~a  ::::::r   )r9   upperr   lenr   any_ALIAS_SUFFIXES)r   r   suffixr   s      @r   _profile_alias_targetr   4  s     t7??3,,22444F<< t3v;;,E )%00 t::::/::::: tLr   r   Optional[Dict[str, str]]c                "   ddl }||n|j        t                      t          | t                    r| ni } t          |           }|sS |                     d          }t          |t                    rt          d |D                       nt                      t          |                     dd                    }|rt          |          nd}d |D             d	 |D             z   }g }	i |D ]|                     j                  }
t          |
t                    r|
ni }
t          |
|          }|	                    |
|f           	                     |
          D ]}                    |j                   # t           $ r Y w xY wt#                      }|	D ]1\  }}}|j        r$|                    d
 |j        D                        2i |	D ],\  }
}t+          j        j        pj        |          j                                       |j        sL	                     |
          n# t           $ r dY nw xY wdddfd}|j                                        D ]\  }}t          |t4                    rt          |t4                    s0 |||          }|r|sAt7          ||          }|r:||vr6|vr2 |||d          r$|j                            d| d| d|d           .S )u  Fetch from every enabled source and apply the merged result to env.

    ``environ`` defaults to ``os.environ``; injectable for tests.

    Precedence per env var (most-specific intent wins):

    1. ``secrets.preserve_existing`` names — a pre-existing env value always
       wins for these, even against a source with ``override_existing: true``
       (escape hatch for profile-local platform secrets, #58073).
    2. Pre-existing env (.env / shell) — unless the winning source has
       ``override_existing: true``.
    3. Mapped sources, in configured order.
    4. Bulk sources, in configured order.

    First claim wins.  A later source that also carries the var gets a
    ``skipped_claimed`` entry and a conflict warning — never a silent
    clobber, and ``override_existing`` never applies across sources.

    Profile aliasing (#51447): when running under a named profile, an applied
    var ``FOO_<PROFILE>`` (credential-shaped suffixes only) also hydrates the
    canonical ``FOO`` so platform adapters and plugins that read fixed env
    names see the profile's value.  The alias obeys the same protected /
    preserve / claimed / override guards and is disabled with
    ``secrets.profile_alias: false``.
    r   Npreserve_existingc              3     K   | ]A}t          |t                    |                                +|                                V  Bd S r4   )rB   r   r   )r   ns     r   r   zapply_all.<locals>.<genexpr>i  s]       $ $:a+=+=$BC''))$			$ $ $ $ $ $r   profile_aliasTr;   c                (    g | ]}|j         d k    |S )r?   r   r   r   s     r   r   zapply_all.<locals>.<listcomp>r  s$    :::aag&9&9&9&9&9r   c                (    g | ]}|j         d k    |S )r@   r   r   s     r   r   zapply_all.<locals>.<listcomp>s  s$    :::6(9(9!(9(9(9r   c              3  D   K   | ]}t          |t                    |V  d S r4   )rB   r   )r   vs     r   r   zapply_all.<locals>.<genexpr>  sE       % %Z3-?-?%% % % % % %r   )r   r"   r#   F)is_aliasr   r   r   r   r   r2   c                  t          |           sj                            |            dS | v rj                            |            dS | v rJj                            |            	j                            |  d|           d
j         d           dS t                              |                     }|r | v rj	                            |            dS |rsj	                            |            dS || <   
j        | <   j
                            |            t          | 
j        
j        |          	j        | <   dS )z=Apply one var through the shared guard chain. True = applied.Fz: kept value from z; uW    also supplies it (first source wins — remove one binding or reorder secrets.sources))r   r   r   r   T)r   r+   r   r*   r)   r1   r   r   rO   r(   r'   r   r   r0   )r   r   r   existedclaimedenvoverridepreserve	protectedreportr   srs       r   
_try_applyzapply_all.<locals>._try_apply  s   $S)) "))#...ui$++C000ug~~"))#... '' E Egcl E E{E E E  
 u3773<<((G 3(??#**3///u x #**3///uCH!;GCLJc"""%/{l$	& & &Fc" 4r   zapplied profile-scoped z as z (active profile ))r   r   r   r   r   r   r2   r   )r   r   r.   rB   r7   r   rO   r,   	frozensetr   r   r   r~   r   protected_env_vars
setdefaultr^   setokupdatesecretsr!   r"   r/   override_existingrH   r   r   warnings)r   re   r   _osr   preserve_rawalias_enabledr   orderedfetchesrd   r#   r   supplied_directlyr<   r   r   r'   r   r   r   r   r   r   r   r   r   s                      @@@@@@@@r   	apply_allr   C  sW   6 (''ckC]]F!+K!>!>F++BK&{33G ??#677L 
L$	'	'9) $ $'$ $ $   -6[[  $??@@M1>F"9---BG ;:7:::::W:::;G =?G "I 	 	oofk**T**2cc$VS)<<V,---	0055 7 7$$S&+66667 	 	 	D	
 !UU  1f9 	$$ % %!>% % %   
 !G& < <Vv{ & ;!') ) ) 	b!!!y 		//44HH 	 	 	HHH	 BG  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	D !...00 	 	JCc3'' z%/E/E  je,,G ' )#w77E &777E<P<P:eUT::: O**8# 8 85 8 8+28 8 8  	 Ms$   3F
FF.III)r   r   r9   r   r2   r   )r   r   r2   rL   )r2   rQ   )r2   rU   )r   r   rd   r7   re   r   r2   r   )r   r7   r2   rQ   )re   r   r2   r   )r   r   r   r   r2   r   r4   )r   r7   re   r   r   r   r2   r.   )*r   
__future__r   concurrent.futuresrm   loggingr   dataclassesr   r   pathlibr   typingr   r   r	   agent.secret_sources.baser
   r   r   r   r   	getLoggerr   rC   r   r   r\   r   r!   r.   rK   rP   rT   rN   rc   r~   r   r   r   r   r   r   r   r   <module>r      s    6 # " " " " "      				 ( ( ( ( ( ( ( (       ' ' ' ' ' ' ' ' ' '              
	8	$	$ %' & & & &          
= 
= 
= 
= 
= 
= 
= 
= 	% 	% 	% 	% 	% 	% 	% 	%" >C . . . . . .b   
# # # #
& & & &B   , , , ,^& & & &R   & I     37I I I I I I Ir   