
    (Gj                        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mZ  ej        e          Z eddh          ZddZ G d d          ZdS )u-  Persistent registry of delivery targets that are confirmed unreachable.

When a messaging platform reports that a target chat is permanently gone — a
deleted group (``Forbidden: the group chat was deleted``), a bot kicked/blocked,
or a deactivated user — re-sending to it on every cron tick or every fan-out
delivery wastes a send attempt against the platform's flood-control envelope and
spams the logs.  This registry lets the delivery layer short-circuit a target it
has already proven dead, while staying self-healing: any successful send to that
target clears the flag, so a user who re-adds the bot (or restores the chat)
recovers automatically with no manual cleanup.

Scope is deliberately narrow.  Only *whole-chat* deaths are recorded — the
``forbidden`` and chat-level ``not_found`` (``chat not found``) error kinds.
Thread/topic-level ``not_found`` is NOT recorded here: the adapters already
self-heal that by retrying without ``reply_to`` (see the Telegram adapter's
reply-target-deleted path), and a deleted topic does not mean the parent chat is
dead.

The store is a small JSON file under the active profile's HERMES_HOME so each
profile keeps its own dead set.  Reads/writes are best-effort: a corrupt or
unwritable file degrades to an in-memory-only registry rather than raising on
the delivery path.
    )annotationsN)Path)DictOptional)get_hermes_home	forbidden	not_foundplatformstrchat_idreturnc                    t          |                                                                            dt          |                                           S )z-Canonical key for a (platform, chat_id) pair.:)r   striplower)r
   r   s     :/home/rurouni/.hermes/hermes-agent/gateway/dead_targets.py
_normalizer   +   sF    (mm!!##))++DDc'll.@.@.B.BDDD    c                  h    e Zd ZdZdddZddZdd	Zedd            ZddZ		 dddZ
ddZddZdS ) DeadTargetRegistryzThread-safe, persistent set of confirmed-dead delivery targets.

    Keyed on ``platform:chat_id``.  Stores the reason and a timestamp for
    observability.  Self-healing: :meth:`clear` (called on a successful send)
    removes the flag.
    NpathOptional[Path]r   Nonec                    t          j                    | _        i | _        ||| _        nt                      dz  dz  | _        |                                  d S )Ngatewayzdead_targets.json)	threadingRLock_lock_dead_pathr   _load)selfr   s     r   __init__zDeadTargetRegistry.__init__8   sP    _&&
35
DJJ(**Y69LLDJ

r   c                   	 | j                                         ret          j        | j                                                   }t          |t                    r'd |                                D             | _        d S d S d S # t          t          f$ r3}t                              d| j         |           i | _        Y d }~d S d }~ww xY w)Nc                D    i | ]\  }}t          |t                    ||S  )
isinstancedict.0kvs      r   
<dictcomp>z,DeadTargetRegistry._load.<locals>.<dictcomp>I   s=     " " "!%A
1d8K8K"1" " "r   u7   dead_targets: could not load %s (%s) — starting empty)r    existsjsonloads	read_textr'   r(   itemsr   OSError
ValueErrorloggerdebug)r"   rawexcs      r   r!   zDeadTargetRegistry._loadC   s    	z  "" j!5!5!7!788c4(( " ")," " "DJJJ	  
 $ 	 	 	LLRS* * *DJJJJJJJ	s   A<B C(CCc                   	 | j         j                            dd           | j                             | j         j        dz             }|                    t          j        | j        d                     |	                    | j                    d S # t          $ r,}t                              d| j         |           Y d }~d S d }~ww xY w)NT)parentsexist_okz.tmp   )indentz'dead_targets: could not persist %s (%s))r    parentmkdirwith_suffixsuffix
write_textr/   dumpsr   replacer3   r5   r6   )r"   tmpr8   s      r   _flush_lockedz DeadTargetRegistry._flush_lockedQ   s    	UJ##D4#@@@*(():V)CDDCNN4:dj;;;<<<KK
##### 	U 	U 	ULLBDJPSTTTTTTTTT	Us   BB 
C
!CC

error_kindOptional[str]boolc                2    t          |           o| t          v S )zEReturn True when ``error_kind`` denotes a permanent whole-chat death.)rI   _DEAD_ERROR_KINDS)rG   s    r   is_dead_error_kindz%DeadTargetRegistry.is_dead_error_kind]   s     JCJ2C$CCr   r
   r   r   c                z    |sdS | j         5  t          ||          | j        v cd d d            S # 1 swxY w Y   d S )NF)r   r   r   )r"   r
   r   s      r   is_deadzDeadTargetRegistry.is_deadb   s     	5Z 	? 	?h00DJ>	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	?s   044 reasonc                   |sdS t          ||          }| j        5  || j        v }t          |                                                                          t          |          t          |          dd         t          j                    d| j        |<   |                                  ddd           n# 1 swxY w Y   |st          	                    d||pd           | S )z@Record a target as confirmed-dead.  Returns True if newly added.FN   )r
   r   rP   	marked_atuv   dead_targets: marked %s as unreachable (%s) — future deliveries to this target will be skipped until a send succeedszno reason given)
r   r   r   r   r   r   timerF   r5   info)r"   r
   r   rP   keyexisteds         r   	mark_deadzDeadTargetRegistry.mark_deadh   s/     	57++Z 	! 	!TZ'GMM//117799w<<f++dsd+!Y[[	 DJsO    	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	!  	KKGV00  
 {s   BB99B= B=c                
   |sdS t          ||          }| j        5  || j        v rE| j        |= |                                  t                              d|           	 ddd           dS 	 ddd           n# 1 swxY w Y   dS )zHRemove a target's dead flag (self-healing).  Returns True if it was set.Fz3dead_targets: cleared %s (delivery succeeded again)NT)r   r   r   rF   r5   rU   )r"   r
   r   rV   s       r   clearzDeadTargetRegistry.clear   s     	57++Z 	 	dj  JsO""$$$QSVWWW	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 us   AA88A<?A<Dict[str, Dict[str, object]]c                    | j         5  d | j                                        D             cddd           S # 1 swxY w Y   dS )zBSnapshot of the current dead set (for diagnostics / `hermes` CLI).c                4    i | ]\  }}|t          |          S r&   )r(   r)   s      r   r-   z/DeadTargetRegistry.all_dead.<locals>.<dictcomp>   s$    >>>41aAtAww>>>r   N)r   r   r2   )r"   s    r   all_deadzDeadTargetRegistry.all_dead   s    Z 	? 	?>>4:+;+;+=+=>>>	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	? 	?s   #8<<)N)r   r   r   r   )r   r   )rG   rH   r   rI   )r
   r   r   rH   r   rI   )rO   )r
   r   r   rH   rP   r   r   rI   )r   r[   )__name__
__module____qualname____doc__r#   r!   rF   staticmethodrL   rN   rX   rZ   r^   r&   r   r   r   r   0   s                U U U U D D D \D? ? ? ? !#    .   ? ? ? ? ? ?r   r   )r
   r   r   r   r   r   )rb   
__future__r   r/   loggingr   rT   pathlibr   typingr   r   hermes_cli.configr   	getLoggerr_   r5   	frozensetrK   r   r   r&   r   r   <module>rk      s   0 # " " " " "              ! ! ! ! ! ! ! ! - - - - - -		8	$	$ I{K899 E E E E
_? _? _? _? _? _? _? _? _? _?r   