
    `gj                        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
  ej                  d      ZdZdZd	 Zdd
ZddZefdd	 	 	 	 	 ddZeefdd	 	 	 	 	 	 	 ddZddZeefdd	 	 	 	 	 	 	 ddZy)uL  Auto-resume restart-loop breaker (#30719, defense-3).

Defenses 1 and 2 (the ``_HERMES_GATEWAY`` guard on ``hermes gateway
stop|restart`` + ``terminal_tool``, and the cron-creation lifecycle
filter) stop the agent from scheduling its own restart via the cron and
CLI paths.  They do NOT cover every SIGTERM source: an agent running a
raw ``terminal("launchctl kickstart -k gui/<uid>/ai.hermes.gateway")``,
an external monitor with a bad trigger, or any other repeated crash can
still drive the supervisor (launchd ``KeepAlive`` / systemd ``Restart=``)
into a tight respawn loop.  On each boot the gateway auto-resumes the
restart-interrupted session, whose next turn re-runs the offending
logic — SIGTERM every ~10 seconds until manually broken.

This module is the last-resort circuit breaker: it records a timestamp
each time the gateway boots with restart-interrupted sessions pending,
keeps a rolling window of recent boots persisted across processes (each
boot is a fresh process, so in-memory state is useless), and reports the
loop as "tripped" once too many such boots happen inside a short window.
When tripped, the caller SKIPS auto-resume for that boot — the gateway
still starts and serves real inbound messages, it just stops replaying
the session that keeps killing it, which breaks the cycle and puts a
human back in the loop.

State lives in ``<HERMES_HOME>/gateway/restart_loop.json`` so it is
profile-scoped and survives process death.  It is intentionally tiny and
best-effort: any read/write failure fails OPEN (no false trip) because a
broken breaker must never wedge a healthy gateway.
    )annotationsN)ListOptionalget_hermes_homezgateway.run   <   c                 "    t               dz  dz  S )Ngatewayzrestart_loop.jsonr        M/root/.hermes/venv/lib/python3.12/site-packages/gateway/restart_loop_guard.py_state_pathr   /   s    y(+>>>r   c                 (   	 t               j                  d      } t        j                  |       }|j	                  dg       }|D cg c]$  }t        |t        t        f      st        |      & c}S c c}w # t        t        t        f$ r g cY S w xY w)Nutf-8encodingboots)r   	read_textjsonloadsget
isinstanceintfloatOSError
ValueError	TypeError)rawdatar   ts       r   _load_bootsr"   3   sy    m%%w%7zz#"%"'GQ:a#u+FaGGGZ+ 	s*   AA8 A3#A30A8 3A8 8BBc                    	 t               }|j                  j                  dd       |j                  t	        j
                  d| i      d       y # t        $ r Y y w xY w)NT)parentsexist_okr   r   r   )r   parentmkdir
write_textr   dumpsr   )r   paths     r   _save_bootsr+   =   sU    }$6

GU#34wG s   AA 	AAnowc                   |t        j                          n|}|t        d|       z
  }t               D cg c]
  }||k\  s	| }}|j                  |       t	        |       |S c c}w )u)  Record that the gateway just booted with restart-interrupted sessions.

    Prunes boots older than ``window_seconds`` and appends the current time.
    Returns the pruned+appended list (most recent last).  Best-effort — a
    persistence failure returns the in-memory list without raising.
       )timemaxr"   appendr+   )window_secondsr-   tscutoffr!   r   s         r   record_restart_interrupted_bootr6   F   s_     B#a((F#51fQ5E5	LLL 6s
   
A#A#c                   | dk  ry|t        j                          n|}|t        d|      z
  }	 t               D cg c]
  }||k\  s	| }}t	        |      | k\  S c c}w # t        $ r Y yw xY w)us  Return True if the gateway has restarted ``>= max_restarts`` times with
    restart-interrupted sessions inside the last ``window_seconds``.

    Reads the persisted boot log written by
    ``record_restart_interrupted_boot`` and counts boots within the window.
    Fails OPEN (returns False) on any error — a broken breaker must never
    wedge a healthy gateway.
    r   Fr/   )r0   r1   r"   	Exceptionlen)max_restartsr3   r-   r4   r5   r!   recents          r   is_restart_loop_trippedr<   Y   sy     qB#a((F(]:a6k!:: v;,&& ; s(   A 
AA
A A 	A+*A+c                 X    	 t               j                  d       y# t        $ r Y yw xY w)zBRemove the persisted boot log (used on clean shutdown / by tests).T)
missing_okN)r   unlinkr   r   r   r   clearr@   r   s,    - s    	))c                   t        ||      }| dkD  rt        |      | k\  nd}|r*t        j                  dt        |      || t	                      |S )aE  Record this restart-interrupted boot and report whether the loop is now
    tripped.

    This is the single entry point the gateway calls: it appends the current
    boot, then checks whether the (now-updated) window has reached the
    threshold.  Returns True when auto-resume should be SKIPPED to break the
    loop.
    r,   r   Fa0  Restart-loop breaker TRIPPED: %d restart-interrupted gateway boots within %ds (threshold %d). Skipping auto-resume to break a suspected SIGTERM-respawn loop (#30719). Restart-interrupted sessions stay resume-pending and will continue on the next real user message. If this is a false positive, delete %s.)r6   r9   loggerwarningr   )r:   r3   r-   r   trippeds        r   check_and_recordrE   z   sW     ,NDE,81,<c%jL(%GD
 JM
	
 Nr   )returnList[float])r   rG   rF   None)r3   r   r-   Optional[float]rF   rG   )r:   r   r3   r   r-   rI   rF   bool)rF   rH   )__doc__
__future__r   r   loggingr0   typingr   r   hermes_constantsr   	getLoggerrB   DEFAULT_MAX_RESTARTSDEFAULT_WINDOW_SECONDSr   r"   r+   r6   r<   r@   rE   r   r   r   <module>rS      s   : #    ! ,			=	)   ? 1   
 	( -0'  	''' 
	'
 
'2 -0  	 
	
 
r   