
    #j?                         d Z ddlZddlZddlmZmZ ddlmZmZ ddl	m
Z
mZmZmZ ddlmZ ddlmZ ddlmZ erdd	lmZ  ej        e          Z G d
 d          ZdZdZdeeef         ddfdZdededefdZdS )u  
Configuration resolution with hierarchical overrides.

Resolves config values through the hierarchy:
  Global (env vars) → Tenant config (via extension) → Bank config (database)

Config values are resolved on every request to ensure consistency across
multiple API servers.
    N)asdictreplace)TYPE_CHECKINGAny)RECALL_BUDGET_FUNCTIONSHindsightConfig_get_raw_confignormalize_config_dict)fq_table)TenantExtension)RequestContext)DatabaseBackendc            	           e Zd ZdZddddedz  fdZddededz  d	efd
Z	ddededz  d	e
eef         fdZded	e
eef         fdZ	 ddede
eef         dedz  d	dfdZded	dfdZdS )ConfigResolverz?Resolves hierarchical configuration with tenant/bank overrides.Nbackendr   tenant_extensionc                     || _         || _        t                      | _        t	          j                    | _        t	          j                    | _        dS )z
        Initialize config resolver.

        Args:
            backend: Database backend for connection acquisition
            tenant_extension: Optional tenant extension for tenant-level config and permissions
        N)	_backendr   r	   _global_configr   get_configurable_fields_configurable_fieldsget_credential_fields_credential_fields)selfr   r   s      e/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/config_resolver.py__init__zConfigResolver.__init__#   sI       0-//$3$K$M$M!"1"G"I"I    bank_idcontextreturnc           	         K   t           j                  } j        r|r	  j                            |           d{V }|rt	          |          } fd|                                D             }|                    |           t                              d| dt          |
                                                      n7# t          $ r*}t                              d| d|            Y d}~nd}~ww xY w                     |           d{V }|rT|                    |           t                              d| dt          |
                                                      t          di |}	|	S )a  
        Resolve full HindsightConfig for a bank with hierarchical overrides applied.

        This is for INTERNAL USE ONLY. Returns the complete config object with all fields
        including credentials and static fields. Use get_bank_config() for API responses.

        Resolution order:
        1. Global config (from environment variables)
        2. Tenant config overrides (from TenantExtension.get_tenant_config())
        3. Bank config overrides (from banks.config JSONB)

        Args:
            bank_id: Bank identifier
            context: Request context for tenant config resolution

        Returns:
            Complete HindsightConfig with hierarchical overrides applied
        Nc                 .    i | ]\  }}|j         v ||S  r   .0kvr   s      r   
<dictcomp>z6ConfigResolver.resolve_full_config.<locals>.<dictcomp>N   s.    *x*x*xDAqYZ^b^wYwYw1aYwYwYwr   z)Applied tenant config overrides for bank : z&Failed to load tenant config for bank z'Applied bank config overrides for bank r#   )r   r   r   get_tenant_configr
   itemsupdateloggerdebuglistkeys	Exceptionwarning_load_bank_configr   )
r   r   r   config_dicttenant_overridesnormalized_tenantconfigurable_tenantebank_overridesresolved_configs
   `         r   resolve_full_configz"ConfigResolver.resolve_full_config1   s     ( T011   	XW 	XX)-)>)P)PQX)Y)Y#Y#Y#Y#Y#Y#Y # (=>N(O(O%*x*x*x*x<M<S<S<U<U*x*x*x'&&':;;;LLqGqqtTgTlTlTnTnOoOoqq    X X XVVVSTVVWWWWWWWWX  $55g>>>>>>>> 	m~...LLk7kkdSaSfSfShShNiNikklll *88K88s   B%C 
C< C77C<c           
      j   K                         ||           d{V }t          |          } fd|                                D             } fd|                                D             } j        r|r	  j                            ||           d{V ^fd|                                D             }t
                              d| dt                     dt          |           d           n7# t          $ r*}t
          	                    d	| d
|            Y d}~nd}~ww xY w|S )a  
        Get fully resolved config for a bank (filtered by permissions).

        Resolution order:
        1. Global config (from environment variables)
        2. Tenant config overrides (from TenantExtension.get_tenant_config())
        3. Bank config overrides (from banks.config JSONB)

        Note: Config is resolved on every call (not cached) to ensure consistency
        across multiple API servers.

        SECURITY:
        - Only returns configurable fields (excludes static/infrastructure fields)
        - Filters out ALL credential fields (API keys, base URLs, etc.)
        - Further filtered by tenant/bank permissions if extension provides them

        Args:
            bank_id: Bank identifier
            context: Request context for tenant config resolution and permissions

        Returns:
            Dict of allowed configurable fields only (never includes credentials or static fields)
        Nc                 .    i | ]\  }}|j         v ||S r#   r$   r%   s      r   r)   z2ConfigResolver.get_bank_config.<locals>.<dictcomp>~   s,    [[[TQAAZ<Z<ZAq<Z<Z<Zr   c                 .    i | ]\  }}|j         v||S r#   )r   r%   s      r   r)   z2ConfigResolver.get_bank_config.<locals>.<dictcomp>   s,    ZZZTQ$BY9Y9YAq9Y9Y9Yr   c                 $    i | ]\  }}|v 	||S r#   r#   )r&   r'   r(   allowed_fieldss      r   r)   z2ConfigResolver.get_bank_config.<locals>.<dictcomp>   s)    YYYAQ.EXEX1EXEXEXr   z#Applied permission filter for bank z
: allowed=z fields, returned=z fieldsz$Failed to load permissions for bank r*   )
r<   r   r,   r   get_allowed_config_fieldsr.   r/   lenr2   r3   )r   r   r   r;   r5   filteredr9   rA   s   `      @r   get_bank_configzConfigResolver.get_bank_configa   s     2 !% 8 8' J JJJJJJJ_-- \[[[[%6%6%8%8[[[ [ZZZX^^%5%5ZZZ   
	VW 
	V	V'+'<'V'VW^`g'h'h!h!h!h!h!h!h!-YYYY1A1AYYYHLL;g ; ;QTUcQdQd ; ;$'MM; ; ;    V V VTgTTQRTTUUUUUUUUV s   :BC< <
D0 D++D0c                 V   K   	  j                                         4 d{V }|                    dt          d           d|           d{V }|rz|d         rr|d         }t	          |t
                    rt          j        |          }t          |          } fd|	                                D             cddd          d{V  S ddd          d{V  n# 1 d{V swxY w Y   n7# t          $ r*}t                              d| d|            Y d}~nd}~ww xY wi S )	z
        Load bank config overrides from banks.config JSONB column.

        Args:
            bank_id: Bank identifier

        Returns:
            Dict of config overrides (only configurable fields, normalized keys)
        Nz(
                    SELECT config FROM banksz( WHERE bank_id = $1
                    configc                 .    i | ]\  }}|j         v ||S r#   r$   r%   s      r   r)   z4ConfigResolver._load_bank_config.<locals>.<dictcomp>   s,    bbbTQ1HaCaCaAqCaCaCar   zFailed to load bank config for r*   )r   acquirefetchrowr   
isinstancestrjsonloadsr
   r,   r2   r.   error)r   r   connrowconfig_data
normalizedr9   s   `      r   r4   z ConfigResolver._load_bank_config   so     	K},,.. c c c c c c c$ MM(0(9(9   	         c3x= c"%h-K "+s33 >&*j&=&= "7{!C!CJ cbbbZ-=-=-?-?bbb'c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c c(  	K 	K 	KLLI7IIaIIJJJJJJJJ	K 	sA   C2 BC ;C2 C2  
C**C2 -C*.C2 2
D&< D!!D&updatesc           
        K   t          |          }t          |                                          | j        z  }|r t	          dt          |           d          t          |                                          | j        z
  }|rt          j                    }||z  }|r_t	          dt          |           dt          t          | j                  dd                    dt          | j                   d          t	          d	t          |           d
t          t          | j                  dd                    d          | j        r|r	 | j                            ||           d{V }	|	ot          |                                          |	z
  }
|
rIt	          |	r8dt          |
           dt          t          |	          dd                    dnd          nA# t          $ r  t          $ r*}t                              d| d|            Y d}~nd}~ww xY wd|v rE|d         =ddlm} 	  ||d                    n$# t          $ r}t	          d|           d}~ww xY wd|v r+|d         r#d |d         D             }|rt	          d          t%          |           | j                                        4 d{V }|                    dt-          d           dt/          j        |          |           d{V  ddd          d{V  n# 1 d{V swxY w Y   t                              d| dt          |                                                      dS )a"  
        Update bank configuration overrides (with permission checking).

        Args:
            bank_id: Bank identifier
            updates: Dict of config field names to new values.
                    Keys can be in env var format (HINDSIGHT_API_LLM_PROVIDER)
                    or Python field format (llm_provider).
                    Only configurable fields are allowed.
            context: Request context for permission checking

        Raises:
            ValueError: If attempting to override invalid/disallowed fields
        z&Cannot set credential fields via API: zE. Credentials (API keys, base URLs) must be set at server level only.z.Cannot override static (server-level) fields: zT. Only configurable fields can be overridden per-bank. Configurable fields include: N
   z... (total: z fields)zUnknown configuration fields: z. Valid configurable fields: z...zNot allowed to modify fields: z. Your permissions allow: zkNot allowed to modify fields: {sorted(disallowed)}. Your permissions do not allow any config modifications.z%Failed to check permissions for bank r*   entity_labels   )parse_entity_labelszInvalid entity_labels format: retain_strategiesc                 T    g | ]%}t          |                                          #|&S r#   )rM   strip)r&   r'   s     r   
<listcomp>z5ConfigResolver.update_bank_config.<locals>.<listcomp>  s3    cccTWXYTZTZT`T`TbTbc!cccr   zXStrategy names must not be empty strings. Remove entries with empty names before saving.
                UPDATE rG   z
                SET config = config || $1::jsonb,
                    updated_at = now()
                WHERE bank_id = $2
                zUpdated bank config for )r
   setr1   r   
ValueErrorsortedr   r   get_static_fieldsr0   rC   r   rB   r2   r.   r3   engine.retain.entity_labelsrZ   _validate_recall_budget_updatesr   rJ   executer   rN   dumpsinfo)r   r   rU   r   normalized_updatescredential_attemptsinvalid_fieldsstatic_fieldsinvalid_staticrA   
disallowedr9   rZ   
empty_keysrQ   s                  r   update_bank_configz!ConfigResolver.update_bank_config   sN     $ 37;; ""4"9"9";";<<t?VV 	W@S9T9T W W W   /446677$:SS 	+=??M+m;N  HVNE[E[ H H4:4@Y;Z;Z[^\^[^;_4`4`H H  #4#<==H H H   !dVN5K5K d d28d>W9X9XY\Z\Y\9]2^2^d d d     	WW 	WW'+'<'V'VW^`g'h'h!h!h!h!h!h!h!-!$%7%<%<%>%>!?!?.!PJ! (  .V ^VJ=O=O ^ ^7=d>>R>RSVTVSV>W7X7X^ ^ ^ ^"V	       W W WUwUURSUUVVVVVVVVW
 0005G5X5dHHHHHHG##$6$GHHHH G G G !E!!E!EFFFG "4449KL_9`4cc%78K%LcccJ  n  
 	((:;;; =((** 
	 
	 
	 
	 
	 
	 
	d,, ))   
-..	 	 	 	 	 	 	 	 	
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 	[w[[$?Q?V?V?X?X:Y:Y[[\\\\\sD   BG( (H&< H!!H&<I 
I/I**I/AL""
L,/L,c                 &  K   | j                                         4 d{V }|                    dt          d           d|           d{V  ddd          d{V  n# 1 d{V swxY w Y   t                              d| d           dS )z
        Reset bank configuration to defaults (remove all overrides).

        Args:
            bank_id: Bank identifier
        Nr_   rG   z
                SET config = '{}'::jsonb,
                    updated_at = now()
                WHERE bank_id = $1
                zReset bank config for z to defaults)r   rJ   rf   r   r.   rh   )r   r   rQ   s      r   reset_bank_configz ConfigResolver.reset_bank_config  sF      =((** 		 		 		 		 		 		 		d,, ))           		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 	BWBBBCCCCCs   .A""
A,/A,)N)__name__
__module____qualname____doc__r   r   rM   r   r   r<   dictr   rE   r4   rp   rr   r#   r   r   r   r       s{       IIJ J 1 J_W[E[ J J J J. . .~PT?T .`o . . . .`/ /S />D;P /\`adfiai\j / / / /b"s "tCH~ " " " "J W[c] c]c]%)#s(^c]>Lt>Sc]	c] c] c] c]JDs Dt D D D D D Dr   r   )recall_budget_fixed_lowrecall_budget_fixed_midrecall_budget_fixed_high)recall_budget_adaptive_lowrecall_budget_adaptive_midrecall_budget_adaptive_highrU   r    c                 l   d| v r_| d         }t          |t                    r|                                t          vr't	          dt          t                     d|          t          D ]R}|| v rL| |         }t          |t                    rt          |t                    s|dk     rt	          | d|          St          D ]Y}|| v rS| |         }t          |t                    s"t          |t          t          f          r|dk    rt	          | d|          ZdD ]R}|| v rL| |         }t          |t                    rt          |t                    s|dk     rt	          | d|          Sd	| v r8d
| v r6| d	         | d
         k    r&t	          d| d	          d| d
          d          dS dS dS )zJValidate recall budget config updates. Raises ValueError on invalid input.recall_budget_functionz&recall_budget_function must be one of z, got rY   z! must be a positive integer, got r   z  must be a positive number, got )recall_budget_minrecall_budget_maxr   r   zrecall_budget_min (z ) must be <= recall_budget_max ()N)rL   rM   lowerr   ra   rb   _RECALL_BUDGET_FIXED_KEYSintbool_RECALL_BUDGET_ADAPTIVE_KEYSfloat)rU   functionkeyvalues       r   re   re   <  sI   7**34(C(( 	HNN,<,<D[,[,[l@W9X9Xll`hll   ) U U'>>CLEeS)) UZt-D-D UPQ		 C!S!S%!S!STTT+ T T'>>CLE%&& Tje.M.M TQVZ[Q[Q[ C!R!R!R!RSSS9 U U'>>CLEeS)) UZt-D-D UPQ		 C!S!S%!S!STTTg%%*=*H*H&''2E*FFFFg.A&B F F&-.A&BF F F   &%*H*HFFr   rH   strategy_namec           	         | j         pi }||vr t                              d| d           | S ||         }t          |t                    s t                              d| d           | S t          j                    fd|                                D             }|s| S t                              d| dt          |
                                                      t          | fi |S )a  
    Apply a named retain strategy's overrides on top of a resolved config.

    A strategy is a named set of hierarchical field overrides stored in
    config.retain_strategies. Any field in _HIERARCHICAL_FIELDS can be
    overridden, including retain_extraction_mode, retain_chunk_size,
    entity_labels, entities_allow_free_form, etc.

    Unknown strategy names log a warning and return config unchanged.
    Unknown or non-hierarchical fields in the strategy are silently ignored.
    zUnknown retain strategy 'z', using resolved config as-iszRetain strategy 'z' is not a dict, skippingc                 $    i | ]\  }}|v 	||S r#   r#   )r&   r'   r(   configurables      r   r)   z"apply_strategy.<locals>.<dictcomp>v  s)    HHHAa<6G6G16G6G6Gr   zApplying retain strategy 'z': )r[   r.   r3   rL   rw   r   r   r,   r/   r0   r1   r   )rH   r   
strategies	overridesrD   r   s        @r   apply_strategyr   _  s    )/RJJ&&`=```aaa=)Ii&& S=SSSTTT":<<LHHHH!2!2HHHH 
LLWmWWX]]__@U@UWWXXX6&&X&&&r   ) rv   rN   loggingdataclassesr   r   typingr   r   hindsight_api.configr   r   r	   r
   "hindsight_api.engine.memory_enginer   hindsight_api.extensions.tenantr   hindsight_api.modelsr   hindsight_api.engine.db.baser   	getLoggerrs   r.   r   r   r   rw   rM   re   r   r#   r   r   <module>r      s      ' ' ' ' ' ' ' ' % % % % % % % %            8 7 7 7 7 7 ; ; ; ; ; ; / / / / / / =<<<<<<		8	$	$MD MD MD MD MD MD MD MD` 
   T#s(^          F'? '3 '? ' ' ' ' ' 'r   