
    sjT                    f   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Zddlm	Z	 ddl
mZmZmZ  ej        e          Z G d de          Z ej        d          Z ej        d	          ZdIdZ eh d          Z eh d          ZdZdZ ed          Z eddh          ZdJdZ eh d          ZdZ  ej        d          Z!dZ"dZ#ee$gee$         f         Z%dKd Z&dLd$Z'dMd%Z(dNd&Z)dId'Z*dMd(Z+dOd+Z,dPd.Z-dd/dQd1Z.dRd3Z/dSd5Z0dTd7Z1dUd8Z2dd9dVd@Z3dddAdWdBZ4dXdCZ5dYdDZ6	 dZd[dHZ7dS )\u~  Gateway lifecycle guard for cron job creation (#30719).

An agent running inside a gateway can schedule a cron job that calls
``hermes gateway restart`` (or ``launchctl kickstart ai.hermes.gateway``
or ``systemctl restart hermes-gateway``).  When the cron fires, the
gateway dies, the supervisor (launchd KeepAlive / systemd Restart=)
revives it, auto-resume picks up the offending session, and the resumed
turn re-runs the same logic — a SIGTERM-respawn loop every ~10 seconds
until manually broken.

This module rejects cron job specs whose prompt or script contains a
direct shell-level gateway-lifecycle command.  It is enforced at
``cron.jobs.create_job`` so it fires on every job-creation path: the
``hermes cron create`` CLI subcommand AND the agent's ``cronjob`` model
tool (which calls ``create_job`` directly, bypassing the CLI layer).

The pattern is intentionally command-shaped: it anchors on a concrete
command identifier (``hermes gateway``, ``launchctl ... hermes-gateway``,
``systemctl ... hermes-gateway``, ``pkill`` against the gateway) so it
cannot fire on prose.  A cron ``prompt`` is fed to a future LLM, not a
shell, so an over-broad substring match on English ("Kong API gateway
autoscaling and restart behavior") would produce a high false-positive
rate without preventing the actual foot-gun, which requires a real
command shape.

This is a defence-in-depth layer.  ``tools/terminal_tool.py`` blocks direct
commands and shell scripts they reference when ``_HERMES_GATEWAY=1``. It also
rejects ``launchctl submit`` in gateway sessions because launchd treats that
primitive as a persistent KeepAlive job, not a one-shot task. ``hermes gateway
stop|restart`` separately refuse to self-target from inside the gateway.
Blocking cron specs at creation time as well means the agent gets an immediate,
informative rejection instead of scheduling a job that will only fail
(silently) when it fires.
    )annotationsN)Path)CallableIteratorOptionalc                      e Zd ZdZdS )GatewayLifecycleBlockedzARaised when a cron job spec contains a gateway-lifecycle command.N)__name__
__module____qualname____doc__     8/home/agent/.hermes/hermes-agent/cron/lifecycle_guard.pyr	   r	   1   s        KKKKr   r	   a8  (?i)(?:hermes\s+gateway\s+(?:restart|stop))|(?:launchctl\s+(?:kickstart|unload|load|stop|restart|submit|bootstrap)\b[^\n]*\bhermes[.\-]?gateway)|(?:systemctl\s+(?:-\S+\s+)*(?:restart|stop|start)\b[^\n]*\bhermes[.\-]?gateway)|(?:p?kill\b[^\n]*\bhermes\b[^\n]*\bgateway)|(?:p?kill\b[^\n]*\bgateway\b[^\n]*\bhermes)z\\\r?\n[ \t]*textstrreturnboolc                    | sdS t                               d|           }t          t                              |                    S )zCReturn True if *text* contains a gateway lifecycle command pattern.F )_SHELL_LINE_CONTINUATIONsubr   _GATEWAY_LIFECYCLE_PATTERNsearchr   
normalizeds     r   "contains_gateway_lifecycle_commandr   b   sA     u)--c488J*11*==>>>r   >   shkshzshbashdash>   +O+o-O-oi      ;&|()zMobile DocumentsCloudStoragepathr   c                b    | j         t          fdt                    D                       S )a8  Return True for paths inside a macOS FileProvider-backed subtree.

    ``O_NONBLOCK`` does not make regular-file reads non-blocking.  Opening an
    evicted FileProvider placeholder below ``~/Library/Mobile Documents``
    (iCloud Drive) or ``~/Library/CloudStorage`` (Dropbox / OneDrive /
    Google Drive and other third-party providers) can therefore wait
    indefinitely for hydration.  The lifecycle guard runs before a terminal
    command's timeout starts, so it must identify this boundary from path
    metadata and fail closed without opening the file.
    c              3  R   K   | ]!\  }}||d z
           dk    o|t           v V  "dS )   LibraryN)_CLOUD_PLACEHOLDER_MARKERS).0indexpartpartss      r   	<genexpr>z-_is_cloud_placeholder_path.<locals>.<genexpr>   sZ        E4eaiI%L$2L*L     r   )r3   any	enumerate)r*   r3   s    @r   _is_cloud_placeholder_pathr7   x   sL     JE    $U++     r   >	   agrgackgreppsqlegrepfgrepsqlite3
journalctl)`z$(z<(z>(z\!zB\|\s*&?\s*(?:sudo\s+)?(?:sh|bash|dash|ksh|zsh|xargs|eval|source)\b)s   ELFs   MZs   s   s   s   s   i   commandIterator[list[str]]c              #    K   |                      dd          }|                                p|gD ]}	 t          j        |dd          }d|_        d|_        t          |          }n# t          $ r Y Dw xY wg }|D ]:}|r!t          |          t          k    r	|r|V  g }%|	                    |           ;|r|V  dS )zEYield shell-tokenized command segments, honoring quotes and comments.z\
 Tr(   posixpunctuation_chars#N)
replace
splitlinesshlexwhitespace_split
commenterslist
ValueErrorset_CONTROL_CHARSappend)rB   r   linelexertokenssegmenttokens          r   _iter_command_segmentsrY      s     ,,J%%''7J<  
	K")  E
 &*E""E%[[FF 	 	 	H	   	" 	"E U~55 !!MMM GNN5!!!! 	MMM- s   4A((
A54A5rW   	list[str]Optional[int]c                b    t          |           D ]\  }}t          j        d|          r|c S dS )z?Return the executable token index after simple env assignments.z^[A-Za-z_][A-Za-z0-9_]*=N)r6   rematch)rW   r1   rX   s      r   _command_token_indexr_      sC    !'**  u8/77 	4r   c                    t          |           D ]`}t          |          }|t          ||                   j        dk    r.||dz   d         }|r|d                                         dv r dS adS )a  Detect an executed ``launchctl submit``/``bootstrap``, not quoted text.

    Label-independent by design: the label of a submitted/bootstrapped job is
    chosen by whoever writes it, so a neutral name (``ai.hermes.svc-reload-tmp``)
    defeats any label-anchored regex (#62891, second reproduction). Both verbs
    register a NEW persistent launchd job (``submit`` jobs get KeepAlive
    semantics; ``bootstrap`` loads an arbitrary plist), which is never safe to
    do from inside the gateway process.
    N	launchctlr-   r   >   submit	bootstrapTF)rY   r_   r   namelower)rB   rW   r1   	argumentss       r   !contains_launchctl_submit_commandrg      s     *'22  $W--=$33	,I Yq\//115LLLtt5r   c                B   g }d}|                                  p| gD ]}t                              |          r|                    |           3	 t	          j        |dd          }d|_        d|_        t          |          }n%# t          $ r |                    |           Y w xY wg }g }|D ]_}|rFt          |          t          k    r.|                    |           |                    |g           g }J|                    |           `|                    |           g }	|D ]}
|
st          |
          }|t          |
|                   j        t          v rh|
|dz   d         }t          d |D                       sBd}|	                    |
d|dz                       |	                    d	 |D                        |	                    |
           |                    d
                    |	                     |s| S d                    |          S )u  Replace data-sink executables' arguments with a neutral placeholder.

    The lifecycle regex is command-shaped, but it cannot tell an EXECUTED
    ``systemctl restart hermes-gateway`` from the same characters appearing
    as *data* — a grep/rg pattern, a journalctl filter, a SQL string literal
    passed to sqlite3/psql. Those diagnostics commands were being rejected
    (false positives blocking legitimate cron prompts), e.g.::

        grep -c 'systemctl restart hermes-gateway' /var/log/syslog
        sqlite3 db "SELECT msg FROM log WHERE msg LIKE '%systemctl restart hermes-gateway%'"

    This masker shell-tokenizes each line and, for command segments whose
    executable is a known data sink (``_DATA_SINK_EXECUTABLES``), replaces
    every argument with ``arg``. The caller then re-runs the lifecycle regex
    on the masked text: a match that survives masking sits OUTSIDE any data
    argument and is a real command.

    Strictly fail-closed: masking is skipped (leaving the original,
    regex-matching text in place) whenever the line pipes into a shell or
    interpreter, any argument carries an execution-capable marker
    (substitution, sqlite3 ``.``-commands, psql ``\!``), or the line cannot
    be tokenized at all. Masking can therefore only ever ALLOW a command the
    plain regex would have blocked — never block one it would have allowed —
    so it runs solely as a second-pass exemption check.
    FTr(   rF   rI   Nr-   c              3     K   | ]9                     d           pt          fdt          D                       V  :dS ).c              3      K   | ]}|v V  	d S Nr   )r0   markerarguments     r   r4   z6_mask_data_sink_arguments.<locals>.<genexpr>.<genexpr>+  s(      UU&6X-UUUUUUr   N)
startswithr5   _UNSAFE_DATA_ARG_MARKERS)r0   rn   s    @r   r4   z,_mask_data_sink_arguments.<locals>.<genexpr>)  sn         ! '',, VUUUU<TUUUUU     r   c              3     K   | ]}d V  dS )argNr   )r0   _s     r   r4   z,_mask_data_sink_arguments.<locals>.<genexpr>0  s"      "<"<Q5"<"<"<"<"<"<r   r   
)rK   _PIPE_TO_INTERPRETERr   rS   rL   rM   rN   rO   rP   rQ   rR   r_   r   rd   _DATA_SINK_EXECUTABLESr5   extendjoin)r   	lines_outchangedrT   rU   rV   segmentscurrentrX   rebuiltrW   r1   rf   s                r   _mask_data_sink_argumentsr~      s~   4 IG!!+dV ), ),&&t,, 	T"""	KDGLLLE%)E""E%[[FF 	 	 	T"""H	 %' 	" 	"E U~55((((((NN5!!!!    	$ 	$G (11E T'%.%9%9%>BX%X%X#EAIKK0	   %.     
 #GNN7;UQY;#7888NN"<"<)"<"<"<<<<NN7####'**++++ 99Ys   4BB'&B'c                    t          |           sdS t                              d|           }t          t          |                    S )uO  Lifecycle-regex scan that exempts matches living inside data arguments.

    Two-pass: the cheap regex first (the overwhelmingly common no-match case
    pays nothing extra); on a raw match, re-scan with data-sink arguments
    masked out. Only a match that survives masking — i.e. one in actual
    command position — blocks.
    Fr   )r   r   r   r~   r   s     r   +_lifecycle_command_scan_with_data_exemptionr   9  sE     .d33 u)--c488J-.G
.S.STTTr   c                >    t          |           pt          |           S )zCPure-string direct scans: lifecycle regex (data-exempted) + submit.)r   rg   )rB   s    r   _direct_lifecycle_scanr   G  s'    6  4	*7	3	34r   	candidateOptional[Path]c                    | rd| v rdS 	 t          |                                           S # t          t          t          f$ r Y dS w xY w)u  Sanitize a tokenized path candidate at the ingestion boundary.

    Candidate tokens come from shlex-splitting arbitrary command text —
    including text recursively decoded from binaries or remote reads — so
    they can carry NUL bytes or other junk no real filesystem path can
    contain. Every OS-facing ``Path`` operation downstream (``expanduser``,
    ``os.open``, ``resolve``) raises a *different* exception for the same
    junk (``ValueError: embedded null byte``, ``RuntimeError: Could not
    determine home directory`` when HOME is unset under launchd, OSError
    for over-long paths). Rejecting here — once, before any OS call — is
    the whole-class fix; catching per-syscall was the whack-a-mole that
    produced #76762, #77703, #77780, and #78256.

    Returns ``None`` for candidates that cannot be a real path (nothing to
    scan), otherwise the ``expanduser()``-expanded ``Path``.
     N)r   
expanduserrP   RuntimeErrorOSError)r   s    r   _expand_candidate_pathr   N  sa    "  )++tI))+++g.   tts    + AAcwdOptional[str]c                    t          |           }|d S |                                s8	 t          |pt          j                              |z  }n# t          $ r Y d S w xY w|S rl   )r   is_absoluter   r   r   )r   r   r*   s      r   _resolve_terminal_script_pathr   g  sz    !),,D|t 	)txzz**T1DD 	 	 	44	 Ks   %A 
AAr   Iterator[Path]c             #    K   t          |           D ]Y}t          |          }|||         }t          |          j        }|dv r6t	          |          |dz   k    rt          ||dz            |          }||V  k|t          v r||dz   d         }d}|t	          |          k     rD||         }	|	dk    r|dz  }n0|	dv rn+|	t          v r|dz  };|	                    d          r|dz  }V	 |t	          |          k     r&||         dvrt          ||         |          }||V  |	                    d	          r/d	|v s|
                    d
          rt          ||          }||V  [dS )z9Yield scripts executed directly or through a POSIX shell.N>   sourcerj   r-   r   z--   -c	--command   -/)z.shz.bashz.zsh)rY   r_   r   rd   lenr   _SHELL_EXECUTABLES_SHELL_OPTIONS_WITH_VALUESro   stripendswith)
rB   r   rW   r1   
executableexecutable_nameresolvedrf   	arg_indexrn   s
             r   _iter_referenced_shell_scriptsr   t  s      *'22 1# 1#$W--=U^
z**/o--7||eai''89KSQQ'"NNN000	,IIc)nn,,$Y/t##NI222999NI&&s++ NI3y>>))i	.B K / / 999MsSS'"NNN C   	#j  J$7$78P$Q$Q 8SII'"NNNc1# 1#r   Iterator[str]c              #    K   t          |           D ]r}t          |          }|!t          ||                   j        t          vr5||dz   d         }t          |dd                   D ]\  }}|dv r||dz            V   nsdS )zDYield code passed through ``sh|bash|... -c`` for recursive scanning.Nr-   r   )rY   r_   r   rd   r   r6   )rB   rW   r1   rf   r   rn   s         r   _iter_shell_command_payloadsr     s      )'22  $W--=D005=OOOEAIKK(	#,Yss^#<#< 	 	Ix...	A.... / r   script_pathc                    	 t          |           }|(|                                rt          |j                  S n# t          $ r Y nw xY wdS )zHReturn the directory *script_path* resolves to, handling relative names.N)_resolve_script_pathr   r   parent	Exception)r   r*   s     r   _resolve_script_directoryr     sa    #K00 0 0 2 2t{###   4s   8< 
A	A	tuple[Optional[str], bool]c                   t          |           rdS 	 |                     d          }n# t          t          f$ r | }Y nw xY wt          |          rdS t          j        t          t          dd          z  }	 t	          j        | |          }n# t          t          f$ r Y dS w xY w	 t	          j        |          }t          j
        |j                  sGt          j        |j                  r	 t	          j        |           dS 	 t	          j        |           dS t	          j        |t                    }|                    t"                    sd|v r	 t	          j        |           dS t%          |          t&          k    rMt	          j        |t&          dz   t%          |          z
            }|sn||z  }t%          |          t&          k    Mn%# t          $ r Y t	          j        |           dS w xY wt	          j        |           n# t	          j        |           w xY wd|v rdS t%          |          t&          k    rdS |                    d	d
          dfS )u  Return ``(text, unsafe)`` using bounded, regular-file-only reads.

    This is the shared choke point for every local script read the guard
    performs (the terminal walk in ``_contains_unsafe_gateway_action`` AND
    the cron-script scan in ``_read_script_for_scanning``), so the
    cloud-placeholder refusal lives here: a FileProvider path must never be
    opened — not even to discover whether the file is hydrated — because an
    evicted placeholder's ``open()`` can hang preflight indefinitely
    (#88052). The lexical check covers direct cloud paths; the resolved
    check covers local launchers that are symlinks into a cloud subtree.
    NTFstrict
O_NONBLOCKr   NF    r-   utf-8rJ   errors)r7   resolver   rP   osO_RDONLYgetattropenfstatstatS_ISREGst_modeS_ISDIRcloseread_BINARY_SNIFF_BYTESro   _BINARY_MAGIC_PREFIXESr   _MAX_REFERENCED_SCRIPT_BYTESdecode)r*   r   flags
descriptormetadatadatachunks          r   _read_referenced_scriptr     s    "$'' z<<u<--Z     	
 "(++ zK'"lA666EWT5))

Z     {{8J''|H,-- 	 |H,-- #", 	+ * 	 wz#677??122 	goo 	 $ii777G81<s4yyH E  EMD $ii777    
 	 ${
4yy///z;;wy;11588sX   * A A 8B B#"B#'AG 8G *A%G H
 
G2H
 1G22H
 
H c                |    | sdS d| v rdS t          |                     dd                    t          k    rdS | dfS )u  Apply the local-read contract to text from a ``read_remote_script`` callback.

    The recursion boundary must not trust its callbacks: any backend (SSH,
    Modal, Daytona, or a future one) can hand back raw binary bytes decoded
    as text, or arbitrarily large output. Mirror
    ``_read_referenced_script``'s semantics exactly — NUL bytes mean binary
    (nothing to scan, checked first, #77703), oversized text fails closed
    like an oversized local file (#76762) — so remote and local reads can
    never diverge again. The size check re-encodes to compare *bytes*
    (matching the local read and the ``head -c`` wire bound): a >1 MiB
    multibyte file truncated at the byte cap decodes to fewer characters
    than bytes, and a character-count check would scan the truncated text
    instead of failing closed. Enforced here rather than inside each
    callback so the guarantee holds for every callback, not just the ones
    we hardened.
    r   r   r   rJ   r   r   F)r   encoder   )r   s    r   _sanitize_remote_script_textr     sR    "  {~~{
4;;wy;11225QQQz;r   )read_remote_scriptdepthintvisited	set[Path]r   Optional[_ReadRemoteScriptFn]c                  t          |           rdS |t          k    rdS t          |           D ]}t          |||dz   ||          r dS t	          | |          D ]}t          |          r dS 	 |                    d          }n# t          t          f$ r |}Y nw xY wt          |          r dS ||v r\|	                    |           t          |          \  }}	|	r dS |/|-t           |t          |                              \  }}	|	r dS |st          t          |                    p|}
t          ||
|dz   ||          r dS dS )NTr-   r   r   r   r   r   Fr   )r   _MAX_REFERENCED_SCRIPT_DEPTHr   _contains_unsafe_gateway_actionr   r7   r   r   rP   addr   r   r   r   )rB   r   r   r   r   payloadr   r   script_textunsafe
script_dirs              r   r   r   .  s    g&& t,,,t/88  *!)1
 
 
 	 44	 6g3GGG . . &k22 	44	#"**%*88HH$ 	# 	# 	# #HHH		#
 &h// 	44wH5kBBV 	44#5#A
 #?""3{#3#344# #K  tt 	 /s8}}==D
*!)1
 
 
 	 44	 5s   1BBB)r   r   c                  	 t          | |dt                      |          S # t          $ r_ t                              dd           	 t          |           cY S # t          $ r# t          |           pt          |           cY cY S w xY ww xY w)uF  Detect lifecycle/submit commands, including bounded nested scripts.

    Total by construction: this function returns a verdict for *every*
    input and never raises. The direct scans below are pure string
    operations; the referenced-script walk touches the filesystem, remote
    backends, and shlex on arbitrary decoded bytes, so it is best-effort
    defense-in-depth — any unexpected failure inside it is logged and
    treated as "walk found nothing" rather than crashing the caller.

    This is the contract #76762 established ("a guarded path must never
    crash the guard") enforced at the boundary instead of per-syscall: a
    guard crash propagates out of ``tools/terminal_tool.py`` and breaks
    every terminal command until the gateway restarts (#77780, #78256),
    which is strictly worse than either verdict.
    r   r   zRlifecycle guard referenced-script walk failed; falling back to direct-scan verdictT)exc_info)r   rQ   r   loggerwarningr   r   rg   )rB   r   r   s      r   7contains_gateway_lifecycle_command_or_referenced_scriptr   w  s    *<.EE1
 
 
 	
  < < <2 	 	
 	
 	
	<)'22222 	< 	< 	< 6  <27;;< < < < <		<<s-   " &B	AB(BBBBc                    ddl m} t          |           }|dS |                                r|S 	  |            dz  |z  S # t          t
          f$ r Y dS w xY w)uI  Resolve a cron ``script`` value the same way the scheduler does.

    The scheduler (``cron.scheduler``) resolves a bare/relative script path
    under ``<HERMES_HOME>/scripts/`` and only accepts absolute paths as-is.
    We MUST mirror that here so the guard scans the file that will actually
    run — otherwise a job whose script lives at the scheduler's real location
    (``~/.hermes/scripts/restart.sh``) but is passed as the bare name
    ``restart.sh`` would read as a nonexistent relative path and silently
    scan prompt-only content, letting the command through.

    Returns ``None`` for values that cannot be a real path (NUL bytes,
    unexpandable ``~``) — the same ingestion contract as
    ``_expand_candidate_path``; such a value can never name a file the
    scheduler would execute, so there is nothing to scan.
    r   )get_hermes_homeNscripts)hermes_constantsr   r   r   r   r   )r   r   raws      r   r   r     s      100000
 
-
-C
{t
 
  9,s22'"    tt	s   A AAc                \    t          |           }|dS t          |          \  }}|rdS |pdS )a  Read a cron script with the bounded terminal-script scanner.

    Non-regular or oversized inputs fail closed by returning a lifecycle-shaped
    sentinel, while missing/unreadable/unresolvable paths remain empty so
    ordinary scheduler path validation can report them.
    NrE   zhermes gateway restart)r   r   )r   r   r   r   s       r   _read_script_for_scanningr     sG     $K00Hr1(;;K (''"r   promptscriptNonec                   | pd}d}|rt          |          }|^	 |                    d          }n# t          t          f$ r |}Y nw xY wt	          |          st	          |          rt          d          |duo
|j        dk    }t          |          }|r| d| }|rt          |          }n$|rt          |          nd}t          ||          }|rt          d	          dS )
aE  Raise ``GatewayLifecycleBlocked`` if *prompt* or *script* contains a
    gateway-lifecycle command pattern.

    ``prompt`` is scanned directly.  ``script``, when supplied, is read from
    disk and concatenated for the scan.  Both are considered together so a
    job cannot slip through by splitting the command across the prompt and
    the script.

    Callers should let the exception propagate when they want the create to
    fail with a ``ValueError``-shaped error (the agent's ``cronjob`` tool
    surfaces this as a tool error; the CLI prints it in red and exits 1).
    rE   FNr   a<  Blocked: the cron script lives on a cloud-synced path (iCloud Drive / ~/Library/CloudStorage). Opening an evicted FileProvider placeholder can hang the guard's preflight scan indefinitely, so it is refused without being read. Move the script to a local, non-cloud path (e.g. ~/.hermes/scripts/) and recreate the job.z.pyrt   r   a  Blocked: cron job contains a gateway lifecycle command or persistent launchctl submit operation. This is blocked to prevent agent-driven SIGTERM-respawn loops under launchd/systemd supervision (#30719). Run `hermes gateway restart` from a shell outside the running gateway instead.)r   r   r   rP   r7   r	   suffixr   r   r   r   )	r   r   combinedpython_scriptresolved_scriptreal_scriptr   r   r   s	            r   check_gateway_lifecycler     sj     |HM 4.v66&.-55U5CCZ( . . .-.)/:: >X? ?  .F   (t3W8NRW8W/77 	4"33k33H 
 =XFF:@J.v666d
H
 
 
  
%+
 
 	

 
s   2 AA)r   r   r   r   )r*   r   r   r   )rB   r   r   rC   )rW   rZ   r   r[   )rB   r   r   r   )r   r   r   r   )r   r   r   r   )r   r   r   r   r   r   )rB   r   r   r   r   r   )rB   r   r   r   )r   r   r   r   )r*   r   r   r   )r   r   r   r   )rB   r   r   r   r   r   r   r   r   r   r   r   )rB   r   r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   rl   )r   r   r   r   r   r   )8r   
__future__r   loggingr   r]   rL   r   pathlibr   typingr   r   r   	getLoggerr
   r   rP   r	   compiler   r   r   	frozensetr   r   r   r   rR   r/   r7   rv   rp   ru   r   r   r   _ReadRemoteScriptFnrY   r_   rg   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s  ! !F # " " " " "  				 				         / / / / / / / / / /		8	$	$L L L L Lj L L L (RZ4  N &2:&677 ? ? ? ? YCCCDD &Y'?'?'?@@ *   7## 'Y(:N'KLL    2 #RRR   :  "rzI     
 uhsm34    8      *H  H  H  H VU U U U4 4 4 4   2
 
 
 
  7# 7# 7# 7# 7# 7#t
 
 
 
   K9 K9 K9 K9\   @ 9=F F F F F FX 8<	-< -< -< -< -< -<d   @   $ !H
 H
 H
 H
 H
 H
 H
r   