
    ݗi\;                       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
 ddlmZmZ ddlmZ dd	lmZmZ  G d
 de      Z G d d      Z G d de      Ze G d d             Z eh d      Z eh d      Z G d de      Z G d de      Zd#dZ G d de      Z G d de      Z G d de      Z G d d e      Z  G d! d"e      Z!y)$u  
openclaw_mcp.interceptor — Middleware interceptor chain for MCP tool calls.

Every tool call entering or leaving OpenClaw passes through this
interceptor chain. Bidirectional:
- PRE-interceptors: run before dispatch (auth, rate limit, schema, log)
- POST-interceptors: run after result (validation, cache, audit, metrics)

Failure Evolution Note (fe_003):
    The GHL MCP sat compiled but unactivated for weeks because Build != Activate.
    This interceptor chain ensures every tool call is OBSERVABLE.
    )annotationsN)ABCabstractmethod)OrderedDict)	dataclassfield)Any   )MCPToolCallMCPToolResultc                  4    e Zd ZdZedd       Zedd       Zy)Interceptorz.Abstract base class for OpenClaw interceptors.c                   K   yw)z3Pre-process. Return None to block, call to proceed.N selfcalls     I/mnt/e/genesis-system/genesis-os/packages/openclaw-mcp/src/interceptor.pypre_callzInterceptor.pre_call"         	   c                   K   yw)z/Post-process. Return modified/unchanged result.Nr   r   r   results      r   	post_callzInterceptor.post_call'   r   r   Nr   r   returnzMCPToolCall | Noner   r   r   r   r   r   )__name__
__module____qualname____doc__r   r   r   r       r   r   r      s+    8   r#   r   c                  B    e Zd ZdZddZd	dZed
d       ZddZddZ	y)InterceptorChaina  
    Ordered chain of interceptors applied to every tool call.

    Usage:
        chain = InterceptorChain()
        chain.register(AuthInterceptor(whitelist=["genesis-orchestrator"]))
        chain.register(RateLimitInterceptor())

        processed_call = await chain.run_pre(call)
        if processed_call is None:
            return error_response("Blocked by interceptor chain")
        result = await dispatch(processed_call)
        final_result = await chain.run_post(processed_call, result)
    c                    g | _         y N)_interceptorsr   s    r   __init__zInterceptorChain.__init__A   s
    02r#   c                :    | j                   j                  |       y)z+Add an interceptor to the end of the chain.N)r(   append)r   interceptors     r   registerzInterceptorChain.registerD   s    !!+.r#   c                ,    t        | j                        S r'   )listr(   r)   s    r   interceptorszInterceptorChain.interceptorsH   s    D&&''r#   c                r   K   |}| j                   D ]  }| y|j                  |       d{   }! |S 7 w)zz
        Run all pre-interceptors in registration order.
        Returns None if any interceptor blocks the call.
        N)r(   r   )r   r   currentr-   s       r   run_prezInterceptorChain.run_preL   sI     
 '+-- 	:K'0099G	:  :s   *75	7c                l   K   |}| j                   D ]  }|j                  ||       d{   } |S 7 w)zn
        Run all post-interceptors in registration order.
        Returns (possibly modified) result.
        N)r(   r   )r   r   r   r3   r-   s        r   run_postzInterceptorChain.run_postX   sD     
 -- 	AK'11$@@G	A As   '42	4Nr   None)r-   r   r   r8   )r   zlist[Interceptor]r   r   )
r   r    r!   r"   r*   r.   propertyr1   r4   r6   r   r#   r   r%   r%   1   s/    3/ ( (
r#   r%   c                  *    e Zd ZdZdddZddZd	dZy)
AuthInterceptorz
    Verify agent has permission to invoke the requested tool.
    Checks agent_id against whitelist. If whitelist is empty, all agents allowed.
    Nc                (    |xs g | _         || _        y r'   )	whitelistsecret)r   r=   r>   s      r   r*   zAuthInterceptor.__init__m   s    "br#   c                \   K   | j                   s|S |j                  | j                   v r|S y wr'   )r=   agent_idr   s     r   r   zAuthInterceptor.pre_callq   s*     ~~K==DNN*Ks   *,c                   K   |S wr'   r   r   s      r   r   zAuthInterceptor.post_callx           )N )r=   zlist[str] | Noner>   strr   r8   r   r   r   r    r!   r"   r*   r   r   r   r#   r   r;   r;   g   s    
r#   r;   c                  @    e Zd ZU dZ ee      Zded<   dd	dZd
dZ	y)_RateLimitBucketz)Sliding window counter for rate limiting.)default_factoryzlist[float]callsc                    t        j                          }||z
  }| j                  D cg c]
  }||kD  s	| c}| _        t        | j                        S c c}w r'   )timerJ   len)r   window_secondsnowcutoffts        r   count_in_windowz _RateLimitBucket.count_in_window   sF    iik~%!%:Aq6za:
4:: ;s
   
AAc                ^    | j                   j                  t        j                                y r'   )rJ   r,   rL   r)   s    r   recordz_RateLimitBucket.record   s    

$))+&r#   N)g      N@)rN   floatr   intr7   )
r   r    r!   r"   r   r0   rJ   __annotations__rR   rT   r   r#   r   rH   rH      s    3t4E;4'r#   rH   >   db_writegit_push	db_delete
file_writefile_deleteplaywright_clickplaywright_navigate>   db_read	cache_get	file_readhealth_checkc                  N    e Zd ZdZ	 	 	 d	 	 	 	 	 	 	 d	dZd
dZddZddZddZy)RateLimitInterceptorz
    Enforce per-agent + per-tool rate limits via sliding window.
    - Default: 100 calls/minute per agent
    - Expensive tools (browser, DB writes): 20 calls/minute
    - Free tools (read-only, cached): 1000 calls/minute
    c                <    || _         || _        || _        i | _        y r'   )default_limitexpensive_limit
free_limit_buckets)r   rf   rg   rh   s       r   r*   zRateLimitInterceptor.__init__   s"     +.$57r#   c                j    |t         v r| j                  S |t        v r| j                  S | j                  S r'   )EXPENSIVE_TOOLSrg   
FREE_TOOLSrh   rf   )r   	tool_names     r   
_get_limitzRateLimitInterceptor._get_limit   s4    ''''
"??"!!!r#   c                8    |j                    d|j                   S )N:)r@   rm   r   s     r   _bucket_keyz RateLimitInterceptor._bucket_key   s    --$..!122r#   c                  K   | j                  |      }|| j                  vrt               | j                  |<   | j                  |   }| j                  |j                        }|j                         |k\  ry |j                          |S wr'   )rq   ri   rH   rn   rm   rR   rT   )r   r   keybucketlimits        r   r   zRateLimitInterceptor.pre_call   su     t$dmm#!1!3DMM#s#/!!#u,s   BB	c                   K   |S wr'   r   r   s      r   r   zRateLimitInterceptor.post_call   rB   rC   N)d        )rf   rV   rg   rV   rh   rV   r   r8   )rm   rE   r   rV   r   r   r   rE   r   r   )	r   r    r!   r"   r*   rn   rq   r   r   r   r#   r   rd   rd      sQ     !!		8	8 	8 		8
 
	8"3r#   rd   c                  2    e Zd ZdZdddZd	dZd
dZddZy)SchemaInterceptorzl
    Validate tool arguments against registered schemas.
    Schemas are loaded from the tool registry.
    Nc                    |xs i | _         y r'   _schemas)r   schemass     r   r*   zSchemaInterceptor.__init__   s    2r#   c                "    || j                   |<   y r'   r~   )r   rm   schemas      r   register_schemaz!SchemaInterceptor.register_schema   s    #)i r#   c                n  K   | j                   j                  |j                        }||S |j                  dg       }|D ]  }||j                  vs y  |j                  di       }|j                  j	                         D ]/  \  }}||v s||   j                  d      }|s"t        ||      r/ y  |S w)Nrequired
propertiestype)r   getrm   	argumentsitems_check_type)r   r   r   r   
field_namer   valueexpected_types           r   r   zSchemaInterceptor.pre_call   s     ""4>>2>K ::j"-" 	J/	
 ZZb1
!%!5!5!7 	 JZ' *: 6 : :6 B UM)J		  s   AB59B5B5#B50B5c                   K   |S wr'   r   r   s      r   r   zSchemaInterceptor.post_call   rB   rC   r'   )r   zdict[str, dict] | Noner   r8   )rm   rE   r   dictr   r8   r   r   )r   r    r!   r"   r*   r   r   r   r   r#   r   r|   r|      s    
&**r#   r|   c                    t         t        t        ft        t        t        t
        d}|j                  |      }|yt        | |      S )zSimple JSON schema type check.)stringnumberintegerbooleanarrayobjectT)rE   rV   rU   boolr0   r   r   
isinstance)r   expectedtype_mapexpected_typess       r   r   r      sG     ,H \\(+Ne^,,r#   c                  :    e Zd ZdZddZedd       Zd	dZd
dZy)LogInterceptorz
    Log every tool call to an audit queue.
    In production: Redis pub/sub for real-time audit stream.
    Here: in-memory list for testability.
    c                    g | _         y r'   )_logr)   s    r   r*   zLogInterceptor.__init__  s	     "	r#   c                ,    t        | j                        S r'   )r0   r   r)   s    r   logzLogInterceptor.log  s    DIIr#   c                   K   | j                   j                  d|j                  |j                  |j                  t        j
                         d       |S w)Npre)phasecall_idrm   r@   ts)r   r,   r   rm   r@   rL   r   s     r   r   zLogInterceptor.pre_call  sE     		||))+
 	 s   AAc           	        K   | j                   j                  d|j                  |j                  |j                  |j
                  t        j                         d       |S w)Npost)r   r   rm   error
elapsed_msr   )r   r,   r   rm   r   r   rL   r   s      r   r   zLogInterceptor.post_call  sN     		||\\ ++))+
 	 s   AA!Nr7   r   z
list[dict]r   r   )	r   r    r!   r"   r*   r9   r   r   r   r   r#   r   r   r     s*    #  	r#   r   c                  *    e Zd ZdZdddZddZd	dZy)
ValidationInterceptorz
    Apply validation on tool output (post-call only).
    In production: invokes sunaiva-crypto 9-layer validation.
    Here: checks for error responses and result size limits.
    c                    || _         y r'   )_max_result_bytes)r   max_result_bytess     r   r*   zValidationInterceptor.__init__4  s
    !1r#   c                   K   |S wr'   r   r   s     r   r   zValidationInterceptor.pre_call7       rC   c                  K   |j                   t        j                  |j                         nd}t        |j	                               | j
                  kD  r/t        |j                  |j                  d d|j                  d       S |S w)NrD   z!Result exceeds maximum size limit)r   rm   r   r   r   sunaiva_signature)
r   jsondumpsrM   encoder   r   r   rm   r   )r   r   r   
result_strs       r   r   zValidationInterceptor.post_call:  sv     28--2KTZZ.QS
z  "#d&<&<<  **9!,,"&  s   BBN)i@B )r   rV   r   r8   r   r   rF   r   r#   r   r   r   -  s    2r#   r   c                  2    e Zd ZdZdddZd	dZd
dZddZy)CacheInterceptorzd
    Cache deterministic tool results.
    Cache key: SHA256(tool_name + sorted_arguments_json)
    Nc                d    t               | _        || _        |xs
 t               | _        d| _        y )Nry   )r   _cache_default_ttlset_cacheable_tools_max_entries)r   default_ttlcacheable_toolss      r   r*   zCacheInterceptor.__init__S  s*    ;F=' / 835 r#   c                    |j                    dt        j                  |j                  d       }t	        j
                  |j                               j                         S )Nrp   T)	sort_keys)rm   r   r   r   hashlibsha256r   	hexdigest)r   r   payloads      r   
_cache_keyzCacheInterceptor._cache_keyY  sF    ^^$Adjj4&P%QR~~gnn./99;;r#   c                (  K   |j                   | j                  vr|S | j                  |      }|| j                  v rT| j                  |   \  }}t	        j                         |z
  | j
                  k  r||j                  d<   |S | j                  |= |S w)N__cached_result)rm   r   r   r   rL   r   r   )r   r   rs   cached_result	cached_ats        r   r   zCacheInterceptor.pre_call]  s     >>!6!66Kood#$++'+{{3'7$M9yy{Y&):):: 5B01 C s   BBc                R  K   |j                   | j                  vr|S |j                  || j                  |      }t	        | j
                        | j                  k\  r| j
                  j                  d       |j                  t        j                         f| j
                  |<   |S w)NF)last)
rm   r   r   r   rM   r   r   popitemr   rL   )r   r   r   rs   s       r   r   zCacheInterceptor.post_callo  s     >>!6!66M<<//$'C4;;4#4#44###/ &tyy{;DKKs   B%B')<   N)r   rV   r   zset[str] | Noner   r8   rz   r   r   )r   r    r!   r"   r*   r   r   r   r   r#   r   r   r   M  s    
!<$
r#   r   c                  :    e Zd ZdZddZedd       Zd	dZd
dZy)AuditInterceptorz
    Write final audit entry for completed tool calls.
    In production: PostgreSQL write.
    Here: in-memory for testability.
    c                    g | _         y r'   )_entriesr)   s    r   r*   zAuditInterceptor.__init__  s	    $&r#   c                ,    t        | j                        S r'   )r0   r   r)   s    r   entrieszAuditInterceptor.entries  s    DMM""r#   c                   K   |S wr'   r   r   s     r   r   zAuditInterceptor.pre_call  r   rC   c                   K   | j                   j                  |j                  |j                  |j                  |j
                  |j                  d u |j                  |j                  d u|j                  d       |S w)N)r   rm   r@   
session_idsuccessr   signedcompleted_at)
r   r,   r   rm   r@   r   r   r   r   r   r   s      r   r   zAuditInterceptor.post_call  sl     ||//||t+ ++..d:"//	
 		 s   A;A=Nr7   r   r   r   )	r   r    r!   r"   r*   r9   r   r   r   r   r#   r   r   r     s*    ' # #r#   r   c                  B    e Zd ZdZddZed	d       Zd
dZddZddZ	y)MetricsInterceptorz
    Update counters for tool call metrics.
    In production: Prometheus labels (tool_name, agent_id, status).
    Here: in-memory counters for testability.
    c                    i | _         y r'   )	_countersr)   s    r   r*   zMetricsInterceptor.__init__  s	    )+r#   c                ,    t        | j                        S r'   )r   r   r)   s    r   counterszMetricsInterceptor.counters  s    DNN##r#   c                \    | j                   j                  |d      dz   | j                   |<   y )Nr   r
   )r   r   )r   rs   s     r   _inczMetricsInterceptor._inc  s&    "nn00a81<sr#   c                   K   | j                  d|j                          | j                  d|j                          |S w)Nzcalls_total:zcalls_by_agent:)r   rm   r@   r   s     r   r   zMetricsInterceptor.pre_call  s:     		L 012		ODMM?34s   ?Ac                   K   |j                   r | j                  d|j                          |S | j                  d|j                          |S w)Nzerrors_total:zsuccess_total:)r   r   rm   r   s      r   r   zMetricsInterceptor.post_call  sK     <<IIdnn%567  IIt~~&678s   AANr7   )r   zdict[str, int])rs   rE   r   r8   r   r   )
r   r    r!   r"   r*   r9   r   r   r   r   r   r#   r   r   r     s/    , $ $=
r#   r   )r   r	   r   rE   r   r   )"r"   
__future__r   r   r   rL   abcr   r   collectionsr   dataclassesr   r   typingr	   serverr   r   r   r%   r;   rH   	frozensetrk   rl   rd   r|   r   r   r   r   r   r   r   r#   r   <module>r      s    #    # # (  .# $/ /lk 2 ' ' '      

,; ,f" "J-(![ !PK @,{ ,f{ F r#   