
    epjE                       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mZ ddlmZmZ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d4dZ"d5dZ#d6dZ$d7dZ%d7d Z&d8d(Z'd9d*Z(d:d,Z)d-Z*d;d1Z+	 d<d=d3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MutableMappingOptional)SECRET_SOURCE_API_VERSION	ErrorKindFetchResultSecretSourceis_valid_env_namereset_source_environmentset_source_environmentz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__     C/home/thesage/.hermes/hermes-agent/agent/secret_sources/registry.pyr   r   7   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$   A   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   r3   )selfs    r"   applied_anyzApplyReport.applied_anyW   s    DO$$$r!   N)r5   r   )r   r   r   r   r   r/   r2   r   dictr3   r4   propertyr9   r    r!   r"   r1   r1   O   s         88"'%"="="=G====(-d(C(C(CJCCCC 5666I6666% % % X% % %r!   r1   )replacer   r   r<   r   r5   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getattrr<   isalnumlowerr   r   items)r   r<   r   rD   
other_nameothers         r"   register_sourcerN   a   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 r7   )_ensure_builtin_sourcesr   get)r   s    r"   
get_sourcerS      s     <<r!   List[SecretSource]c                 j    t                       t          t                                                    S r7   )rQ   r/   r   valuesr    r!   r"   list_sourcesrW      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.bitwardenrZ   rN   	ExceptionrF   rG    agent.secret_sources.onepasswordr]   agent.secret_sources.commandr^   )rZ   r]   r^   s      r"   rQ   rQ      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_testsrf      s    NNr!   cfgr:   	home_pathr   environMutableMapping[str, str]r   c                                                    }t          j                            dd j                   }	 d fd}|                    |          }	 |                    |          }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_prefixr5   r   c                     t                    } 	                               t          |            S # t          |            w xY wr7   )r   fetchr   )tokenrg   ri   rh   r   s    r"   _fetchz#_fetch_with_timeout.<locals>._fetch   sL    *733E0||C33(////(////s	   7 A)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)r5   r   )fetch_timeout_seconds
concurrentfuturesThreadPoolExecutorr   submitr&   TimeoutErrorcancelr   errorr   TIMEOUT
error_kindshutdownra   typer   INTERNALrE   )r   rg   rh   ri   rs   executorrr   futurer&   resexcs   ````       r"   _fetch_with_timeoutr      sA    **3//G!44*E*E*E 5  H&	0 	0 	0 	0 	0 	0 	0 	0 	0 ((	]]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   E3 #A: 9E3 :AEE3 /	E8?E7E8E3 EE3 3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`.
    r2   c                N    g | ]"}t          |t                    r|t          v |#S r    )rE   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; skippingTr[   )rQ   rR   rE   r/   r   r   appendrF   rG   joinr:   
is_enabledra   )	r   explicitorderentryunknownr   enabledr   rg   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   osri   rR   strip)rh   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 r7   )endswith)r   saliass     r"   	<genexpr>z(_profile_alias_target.<locals>.<genexpr>H  s/      ::Qu~~a  ::::::r!   )r<   upperr   lenr   any_ALIAS_SUFFIXES)r   r   suffixr   s      @r"   _profile_alias_targetr   >  s     t7??3,,22444F<< t3v;;,E )%00 t::::/::::: tLr!   "Optional[MutableMapping[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 r7   )rE   r   r   )r   ns     r"   r   zapply_all.<locals>.<genexpr>s  s]       $ $:a+=+=$BC''))$			$ $ $ $ $ $r!   profile_aliasTr>   c                (    g | ]}|j         d k    |S )rB   r   r   r   s     r"   r   zapply_all.<locals>.<listcomp>|  s$    :::aag&9&9&9&9&9r!   c                (    g | ]}|j         d k    |S )rC   r   r   s     r"   r   zapply_all.<locals>.<listcomp>}  s$    :::6(9(9!(9(9(9r!   c              3  D   K   | ]}t          |t                    |V  d S r7   )rE   r   )r   vs     r"   r   zapply_all.<locals>.<genexpr>  sE       % %Z3-?-?%% % % % % %r!   )r   r%   r&   F)is_aliasr   r   r   r   r   r5   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,   r4   r   r   rR   r+   r*   r   r   r3   )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   r5   r   )r   ri   r1   rE   r:   r   rR   r/   	frozensetr   r   r   r   r   protected_env_vars
setdefaultra   setokupdatesecretsr$   r%   r2   override_existingrK   r   r   warnings)r   rh   ri   _osr   preserve_rawalias_enabledr   orderedfetchesrg   r&   r   supplied_directlyr?   r   r   r*   r   r   r   r   r   r   r   r   r   s                      @@@@@@@@r"   	apply_allr   M  sY   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)SAA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   r<   r   r5   r   )r   r   r5   rO   )r5   rT   )r5   rX   )
r   r   rg   r:   rh   r   ri   rj   r5   r   )r   r:   r5   rT   )rh   r   r5   r   )r   r   r   r   r5   r   r7   )r   r:   rh   r   ri   r   r5   r1   )-r   
__future__r   concurrent.futuresrv   loggingr   dataclassesr   r   pathlibr   typingr   r   r	   r
   agent.secret_sources.baser   r   r   r   r   r   r   	getLoggerr   rF   r   r   r_   r   r$   r1   rN   rS   rW   rQ   rf   r   r   r   r   r   r   r    r!   r"   <module>r      s    6 # " " " " "      				 ( ( ( ( ( ( ( (       7 7 7 7 7 7 7 7 7 7 7 7                  
	8	$	$ %' & & & &          
= 
= 
= 
= 
= 
= 
= 
= 	% 	% 	% 	% 	% 	% 	% 	%" >C . . . . . .b   
# # # #
& & & &B   4 4 4 4n& & & &R   & I     =AI I I I I I Ir!   