Ë
    âß²j<  ã                  ó\  — U 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
mZ ddlZddlmZ  e«        ddlmZ  ej"                  e«      Z ed«      Z ej*                  «       Zi Zd	ed
<   i Zd	ed<   dd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Z d d„Z!d!d„Z"d"d#d„Z#d$d„Z$d%d„Z%d&d„Z&y)'uÔ  Managed scope â€” IT-pushed, user-immutable config & env layer.

DISTINCT from ``hermes_cli.config.is_managed()`` / ``HERMES_MANAGED`` (a coarse package-manager
write-lock that blocks all mutation); this layer injects specific immutable values. The two are
independent and may coexist. v1 enforcement is filesystem permissions only (see
``docs/design/managed-scope.md`` Â§7); ``get_managed_dir()`` is the single seam for adding
macOS / Windows native locations later.
é    )ÚannotationsN)ÚPath)ÚDictÚOptional)Údrop_stale_root_modules)Úfile_signaturez/etc/hermesúDict[str, tuple]Ú_CONFIG_CACHEÚ
_ENV_CACHEc                 ó&   — dt         j                  v S )zºTrue inside the test suite: ignore the system ``/etc/hermes`` so a real managed scope on a
    dev/CI box can't leak policy into the suite. An explicit ``HERMES_MANAGED_DIR`` still wins.ÚPYTEST_CURRENT_TEST)ÚosÚenviron© ó    ú=/home/ubuntu/.hermes/hermes-agent/hermes_cli/managed_scope.pyÚ_under_pytestr   (   s   € ð !¤B§J¡JÐ.Ð.r   c                 óÄ   — t         j                  j                  dd«      j                  «       } | rt	        | «      }nt        «       ryt        }|j                  «       r|S dS )ur  Resolve the managed-scope directory, or None when no scope is present.

    Priority: ``$HERMES_MANAGED_DIR`` (IT-only bootstrap override; never persisted to any .env;
    honored only when non-empty AND the directory exists), then ``/etc/hermes`` when it exists.
    A missing directory resolves to None â€” the common case, so it must be cheap + side-effect-free.
    ÚHERMES_MANAGED_DIRÚ N)r   r   ÚgetÚstripr   r   Ú_DEFAULT_MANAGED_DIRÚis_dir)ÚoverrideÚps     r   Úget_managed_dirr   .   sN   € ô �z‰z�~‰~Ð2°BÓ7×=Ñ=Ó?€HÙÜ�‹N‰Ü	ŒØä ˆØ—‘”
ˆ1Ð$ Ð$r   c                 óŠ   — t         5  t        j                  «        t        j                  «        ddd«       y# 1 sw Y   yxY w)z@Drop cached managed config/env. For tests and post-edit reloads.N)Ú_CACHE_LOCKr
   Úclearr   r   r   r   Úinvalidate_managed_cacher!   ?   s2   € ä	ñ Ü×ÑÔÜ×ÑÔ÷÷ ñ ús	   ‡)9¹Ac                ó"  — 	 | j                  «       }t        |«      }t        | «      }t        5  |j                  |«      }|�;|dt        |«       |k(  r*t        j                  |t        |«         «      cddd«       S ddd«       	  || «      }t        5  g |¢t        j                  |«      ‘­||<   ddd«       |S # t        $ r Y yw xY w# 1 sw Y   ŒOxY w# t        $ r!}t        j                  d| |«       Y d}~yd}~ww xY w# 1 sw Y   |S xY w)u:  Shared stat-signature-keyed read; returns a deepcopy of the parsed value.

    ``None`` when the file is absent or fails to parse (fail-open). A parse failure is logged
    LOUDLY â€” the admin needs to know their policy isn't applied â€” but never raises, so a malformed
    managed file can't brick startup.
    Nuˆ   managed scope: failed to parse %s: %s â€” IGNORING this managed file. Admin policy from this file is NOT being applied. Fix and restart.)ÚstatÚOSErrorr   Ústrr   r   ÚlenÚcopyÚdeepcopyÚ	ExceptionÚloggerÚwarning)	ÚpathÚcacheÚparseÚstÚkeyÚpath_keyÚhitÚparsedÚexcs	            r   Ú_cached_readr5   F   s  € ðØ�Y‰Y‹[ˆô ˜Ó
€CÜ�4‹y€HÜ	ñ 0Ø�i‰i˜Ó!ˆØˆ?˜s 9¤C¨£H˜~°Ò4Ü—=‘= ¤S¨£X¡Ó/÷0÷ 0ñ 0ðÙ�t“ˆô 
ñ 8Ø7˜CÐ7¤§¡¨vÓ!6Ñ7ˆˆh‰÷8à€Møô% ò Ùðú÷0ð 0ûô ò Ü�‰ðQà�#ô	ô ûðú÷8à€MúsA   ‚B< ®ACÂC ÂDÂ<	CÃCÃCÃ	DÃ C<Ã<DÄDc                óf   — t        «       }|€i S t        || z  ||«      }t        |t        «      r|S i S )N)r   r5   Ú
isinstanceÚdict)Únamer-   r.   Úmanaged_dirr3   s        r   Ú_load_managed_filer;   d   s<   € Ü!Ó#€KØÐØˆ	Ü˜+¨Ñ,¨e°UÓ;€FÜ ¬Ô-ˆ6Ð5°2Ð5r   c                 ó&   — t        dt        d„ «      S )zDParsed managed config.yaml, or {} when absent/malformed (fail-open).zconfig.yamlc                óT   — t        j                  | j                  d¬«      «      xs i S )Nzutf-8©Úencoding)ÚyamlÚ	safe_loadÚ	read_text)r   s    r   ú<lambda>z%load_managed_config.<locals>.<lambda>n   s%   € ÄdÇnÁnÐUV×U`ÑU`ÐjqÐU`ÓUrÓFsÒFyÐwy€ r   )r;   r
   r   r   r   Úload_managed_configrD   l   s   € ä˜m¬]Ñ<yÓzÐzr   c                 ó,   — t        dt        t        «      S )z?Parsed managed .env (KEY=VALUE), or {} when absent (fail-open).z.env)r;   r   Ú_parse_managed_envr   r   r   Úload_managed_envrG   q   s   € ä˜f¤jÔ2DÓEÐEr   c                óB   — ddl m} | j                  d¬«        || «      S )Nr   )Úload_env_filez	utf-8-sigr>   )Úagent.secret_scoperI   rB   )r,   rI   s     r   rF   rF   v   s   € Ý0à‡N�N˜K€NÔ(Ù˜ÓÐr   c                ó  — 	 t        «       }|s| S ddlm}m}m}  | ||«      «      }t        |j                  d«      t        «      rt        |«      }d|d   i|d<    || |«      S # t        $ r t        j                  dd¬«       | cY S w xY w)a-  Overlay administrator-pinned config values on top of an already-built dict.

    ``${VAR}`` refs in the managed config expand against the PROCESS env only, so a user cannot
    shadow a managed literal via a ref they control; a bare root ``model: x/y`` string is promoted
    to ``model.default`` so it can't clobber the dict shape callers expect; managed values
    deep-merge ON TOP per leaf while sibling keys stay user-controlled. Fail-open: returns
    ``config`` unchanged when no scope is present or on any error. Mutates and returns ``config``.
    r   )Ú_deep_mergeÚ_expand_env_varsÚ_normalize_root_model_keysÚmodelÚdefaultz-managed scope: failed to apply config overlayT)Úexc_info)rD   Úhermes_cli.configrL   rM   rN   r7   r   r%   r8   r)   r*   r+   )ÚconfigÚmanagedrL   rM   rN   Úmanaged_expandeds         r   Úapply_managed_overlayrV   }   sœ   € ðÜ%Ó'ˆÙØˆMç_Ñ_Ù5Ñ6FÀwÓ6OÓPÐô Ð&×*Ñ*¨7Ó3´SÔ9Ü#Ð$4Ó5ÐØ)2Ð4DÀWÑ4MÐ(NÐ˜WÑ%Ù˜6Ð#3Ó4Ð4øÜò Ü�‰ÐFÐQUˆÔVØŠðús   ‚A% �AA% Á%"B
Â	B
c                óØ   — t        «       }| j                  «       D ]L  \  }}|r|› d|› �n
t        |«      }t        |t        «      r|r|t        ||«      z  }Œ<|j                  |«       ŒN |S )Nú.)ÚsetÚitemsr%   r7   r8   Ú_flatten_keysÚadd)ÚdÚprefixÚkeysÚkÚvÚdotteds         r   r[   r[   ™   si   € Ü“€DØ—‘“	ò ‰ˆˆ1Ù$*�F�8˜1˜Q˜C‘´°A³ˆÜ�aœÔ¡1Ø”M ! VÓ,Ñ,‰Dà�H‰H�VÕðð €Kr   c                 ó(   — t        t        «       «      S )zGDotted leaf keys pinned by the managed config (e.g. {'model.default'}).)r[   rD   r   r   r   Úmanaged_config_keysrd   ¤   s   € äÔ,Ó.Ó/Ð/r   c                ó   — | t        «       v S )zCTrue if the exact dotted config key is pinned by the managed layer.)rd   )Ú
dotted_keys    r   Úis_key_managedrg   ©   s   € àÔ,Ó.Ð.Ð.r   c                ó   — | t        «       v S )z=True if the env var name is pinned by the managed .env layer.)rG   )r9   s    r   Úis_env_managedri   ®   s   € àÔ#Ó%Ð%Ð%r   )ÚreturnÚbool)rj   zOptional[Path])rj   ÚNone)r,   r   r-   r	   )r9   r%   r-   r	   rj   r8   )rj   r8   )rj   úDict[str, str])r,   r   rj   rm   )rS   r8   rj   r8   )r   )r]   r8   r^   r%   rj   rY   )rj   rY   )rf   r%   rj   rk   )r9   r%   rj   rk   )'Ú__doc__Ú
__future__r   r'   Úloggingr   Ú	threadingÚpathlibr   Útypingr   r   r@   Úhermes_cli.stale_modulesr   Úutilsr   Ú	getLoggerÚ__name__r*   r   ÚLockr   r
   Ú__annotations__r   r   r   r!   r5   r;   rD   rG   rF   rV   r[   rd   rg   ri   r   r   r   ú<module>rz      s¸   ðòõ #ã Û Û 	Û Ý ß !ã õ
 =á Ô å  à	ˆ×	Ñ	˜8Ó	$€ñ ˜MÓ*Ð àˆi�n‰nÓ€à"$€ÐÓ $Ø!€
ÐÓ !ó/ó%ó"óó<6ó{ó
Fó
óô8ó0ó
/ô
&r   