Ë
    ÛJêjáG  ã                  ó\  — 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m	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  ej(                  e«      Zd	Z G d
„ dee«      Ze G d„ d«      «       Ze G d„ d«      «       Ze G d„ d«      «       Z G d„ de«      Z G d„ d«      Z G d„ d«      Zg d¢Z y)u  API Gateway â€” control plane HTTP/gRPC gateway for external access.

Provides:
- REST API for agent orchestration, task submission, status queries
- Authentication & authorization middleware
- Request validation and rate limiting
- OpenAPI schema generation
é    )ÚannotationsN)Ú	AwaitableÚCallable)Ú	dataclassÚfield)ÚEnum)ÚAnyÚProtocol)Úuuid4Úunidentifiedc                  ó    — e Zd ZdZdZdZdZdZy)Ú
HTTPMethodÚGETÚPOSTÚPUTÚPATCHÚDELETEN)Ú__name__Ú
__module__Ú__qualname__r   r   r   r   r   © ó    úM/home/oem/CyberBrain26-unified/src/cyberbrain26/control_plane/api/__init__.pyr   r      s   „ Ø
€CØ€DØ
€CØ€EØ�Fr   r   c                  ó€   — e Zd ZU dZded<   ded<   ded<   ded<   d	Zded
<    ee¬«      Zded<   dZ	ded<   dZ
ded<   y)ÚRoutezAPI route definition.ÚstrÚpathr   ÚmethodúCallable[..., Awaitable[Any]]ÚhandlerÚnameÚ Úsummary©Údefault_factoryz	list[str]ÚtagsTÚboolÚrequires_authNú
int | NoneÚ
rate_limit)r   r   r   Ú__doc__Ú__annotations__r#   r   Úlistr&   r(   r*   r   r   r   r   r   '   sH   … áØ
ƒIØÓØ*Ó*Ø
ƒIØ€GˆSÓÙ¨DÔ1€Dˆ)Ó1Ø€M�4ÓØ!€J�
Ô!r   r   c                  óŒ   — e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<    ee¬«      Zded<   dZded<    ed„ ¬«      Z	ded<   y)Ú
APIRequestav  Incoming API request.

    ``client_addr`` is the caller-supplied remote address (when available) used
    as the rate-limit key for anonymous traffic. ``request_id`` is an opaque
    correlation id for the request and is deliberately never used as a
    rate-limit key (a per-request UUID would create a fresh empty bucket each
    call and make limiting ineffective).
    r   r   r   r   údict[str, str]ÚheadersÚquery_paramsr	   Úbodyr$   Úpath_paramsNú
str | NoneÚclient_addrc                 ó*   — t        «       j                  S ©N)r   Úhexr   r   r   ú<lambda>zAPIRequest.<lambda>E   s   € ´E³G·K±K€ r   Ú
request_id)
r   r   r   r+   r,   r   Údictr4   r6   r;   r   r   r   r/   r/   4   sN   … ñð ÓØ
ƒIØÓØ Ó Ø
ƒIÙ"'¸Ô"=€K�Ó=Ø"€K�Ó"ÙÑ,?Ô@€J�Ô@r   r/   c                  óP   — e Zd ZU dZded<   ded<    ee¬«      Zded<   d	Zd
ed<   y)ÚAPIResponsezAPI response.ÚintÚstatus_coder	   r3   r$   r0   r1   r"   r   r;   N)	r   r   r   r+   r,   r   r<   r1   r;   r   r   r   r>   r>   H   s)   … áØÓØ
ƒIÙ#°DÔ9€Gˆ^Ó9Ø€J�Ôr   r>   c                  ó   — e Zd ZdZdd„Zy)ÚAuthProviderz!Authentication provider protocol.c              ƒ  ó   K  — y­w)zm
        Authenticate a request.
        Returns (user_id, claims) or (None, {}) if unauthenticated.
        Nr   )ÚselfÚrequests     r   ÚauthenticatezAuthProvider.authenticateT   s   è ø€ ð
 	ùs   ‚N©rE   r/   Úreturnz!tuple[str | None, dict[str, Any]])r   r   r   r+   rF   r   r   r   rB   rB   Q   s
   „ Ù+ôr   rB   c                  ó²   — e Zd ZdZ	 	 ddddœ	 	 	 	 	 	 	 	 	 dd„Zddddd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d„Zy)Ú
APIGatewayaD  HTTP API Gateway for CyberBrain26 control plane.

    Routes requests to registered handlers, enforces auth,
    validates input, and applies rate limiting.

    Security model
    -------------
    The gateway is fail-closed by default:

    * A route with ``requires_auth=True`` is denied (HTTP 401) when no
      ``auth_provider`` is configured, rather than silently bypassing
      authentication. Anonymous access is never the implicit fallback when
      ``requires_auth`` is set; this closes the fail-open bypass where a
      gateway constructed without a provider would serve every authenticated
      route to anonymous callers.
    * Anonymous routes (``requires_auth=False``) are served only when the
      caller explicitly opts in via ``allow_unauthenticated=True``. This makes
      public access intentional and documented instead of accidental, so a
      forgotten provider does not silently expose routes.
    * Handler exception text is never returned to clients. A generic error
      plus an opaque ``correlation_id`` (the request id) is returned instead;
      exception detail is disclosed only when ``debug=True`` is explicitly set.
    * Rate limiting is keyed on the authenticated principal (``user_id``) or
      the caller-supplied ``client_addr``; it is never keyed on the per-request
      ``request_id``.
    * Rate limiting is never silently skipped. A request that carries neither a
      principal nor a ``client_addr`` is throttled through one documented
      shared ``unidentified`` bucket rather than escaping the limiter; see
      ``_derive_rate_limit_key``.
    NF)Úallow_unauthenticatedÚdebugc               óX   — i | _         || _        || _        || _        || _        i | _        y r8   )Ú_routesÚ_auth_providerÚ_default_rate_limitÚ_allow_unauthenticatedÚ_debugÚ_rate_limit_buckets)rD   Úauth_providerÚdefault_rate_limitrK   rL   s        r   Ú__init__zAPIGateway.__init__|   s6   € ð *,ˆŒØ+ˆÔØ#5ˆÔ ð '<ˆÔ#ð ˆŒØ;=ˆÕ r   r"   T)r!   r#   r&   r(   r*   c               óR  — |j                   › d|› �}	t        ||||xs> |j                   j                  «       › d|j                  d«      j	                  dd«      › �||xs g ||xs | j
                  ¬«      }
|
| j                  |	<   t        j                  d|j                   |«       |
S )zRegister a route.ú:Ú_ú/)r   r   r    r!   r#   r&   r(   r*   zRegistered route %s %s)	Úvaluer   ÚlowerÚstripÚreplacerP   rN   ÚloggerÚinfo)rD   r   r   r    r!   r#   r&   r(   r*   Ú	route_keyÚroutes              r   Ú	add_routezAPIGateway.add_route�   s¨   € ð —|‘|�n A d VÐ,ˆ	ÜØØØØÒV˜FŸL™L×.Ñ.Ó0Ð1°°4·:±:¸c³?×3JÑ3JÈ3ÐPSÓ3TÐ2UÐVØØ’˜Ø'Ø!Ò= T×%=Ñ%=ô	
ˆð #(ˆ�‰�YÑÜ�‰Ð,¨f¯l©l¸DÔAØˆr   c                ó  — |j                   › d|› �}|| j                  v r| j                  |   S | j                  j                  «       D ]>  \  }}|j                  |k7  rŒ| j	                  |j                  dd«      d   |«      sŒ<|c S  y)z2Find route for path/method (supports path params).rX   é   N)r[   rN   Úitemsr   Ú_match_pathÚsplit)rD   r   r   ra   Úregistered_pathrb   s         r   rb   zAPIGateway.route«   s�   € à—|‘|�n A d VÐ,ˆ	Ø˜Ÿ™Ñ$Ø—<‘< 	Ñ*Ð*ð '+§l¡l×&8Ñ&8Ó&:ò 	Ñ"ˆO˜UØ�|‰|˜vÒ%ØØ×Ñ × 5Ñ 5°c¸1Ó =¸aÑ @À$ÕGØ’ð		ð
 r   c                óÒ   — |j                  d«      }|j                  d«      }t        |«      t        |«      k7  ryt        ||«      D ]  \  }}|j                  d«      rŒ||k7  sŒ y y)aS  Simple path parameter matching.

        Splits both pattern and path on ``/`` and compares segment by segment.
        Because the segment counts must be equal and every non-parameter
        segment must match exactly, this avoids the prefix-matching foot-gun
        where ``/admin`` would erroneously match ``/administrator``.
        rZ   Fú{T)rh   ÚlenÚzipÚ
startswith)rD   Úpatternr   Úpattern_partsÚ
path_partsÚp_patÚp_paths          r   rg   zAPIGateway._match_path¹   sm   € ð  Ÿ™ cÓ*ˆØ—Z‘Z “_ˆ
Üˆ}Ó¤ Z£Ò0ØÜ  °
Ó;ò 	‰MˆE�6Ø×Ñ Ô$ØØ˜‹Ùð		ð
 r   c                óÒ   — i }|j                  d«      }|j                  d«      }t        ||«      D ]3  \  }}|j                  d«      sŒ|j                  d«      sŒ*|dd }|||<   Œ5 |S )z+Extract path parameters from matched route.rZ   rk   ú}re   éÿÿÿÿ)rh   rm   rn   Úendswith)	rD   ro   r   Úparamsrp   rq   rr   rs   Ú
param_names	            r   Ú_extract_path_paramszAPIGateway._extract_path_paramsÌ   sq   € àˆØŸ™ cÓ*ˆØ—Z‘Z “_ˆ
Ü  °
Ó;ò 	,‰MˆE�6Ø×Ñ Õ$¨¯©¸Õ)<Ø" 1 R˜[�
Ø%+��zÒ"ð	,ð ˆr   c              ƒ  ó°  K  — | j                  |j                  |j                  «      }|st        dddi|j                  ¬«      S d}i }|j
                  rh| j                  €t        dddi|j                  ¬«      S | j                  j                  |«      ƒ d{  –—† \  }}|€@t        dddi|j                  ¬«      S | j                  st        dddi|j                  ¬«      S | j                  ||j                  «      }|j                  r9| j                  ||j                  «      st        ddd	id
di|j                  ¬«      S 	 |||| j                  |j                  |j                  «      dœ} |j                  di |¤Žƒ d{  –—† }t        |t        «      r|j                  |_        |S t        d||j                  ¬«      S 7 �Œ,7 ŒB# t         $ r€}t"        j%                  d|j                  j&                  |j                  «       d|j                  dœ}	| j(                  rt+        |«      |	d<   t        d|	|j                  ¬«      cY d}~S d}~ww xY w­w)zProcess an incoming request.i”  Úerrorz	Not found)r@   r3   r;   Ni‘  ÚUnauthorizedi­  zRate limit exceededzRetry-AfterÚ60)r@   r3   r1   r;   )rE   Úuser_idÚclaimsr4   éÈ   zHandler error for %s %szInternal server error)r|   Úcorrelation_idÚdetailiô  r   )rb   r   r   r>   r;   r(   rO   rF   rQ   Ú_derive_rate_limit_keyr6   r*   Ú_check_rate_limitrz   r    Ú
isinstanceÚ	Exceptionr_   Ú	exceptionr[   rR   r   )
rD   rE   rb   r   r€   Úrate_keyÚcontextÚresultÚexcr3   s
             r   ÚhandlezAPIGateway.handle×   sO  è ø€ à—
‘
˜7Ÿ<™<¨¯©Ó8ˆÙÜ¨3°g¸{Ð5KÐX_×XjÑXjÔkÐkð #ˆØ!#ˆØ×Òð ×"Ñ"Ð*Ü"Ø #Ø! >Ð2Ø&×1Ñ1ôð ð
 %)×$7Ñ$7×$DÑ$DÀWÓ$M×M‰OˆG�VØˆÜ"Ø #Ø! >Ð2Ø&×1Ñ1ôð ð
 ×,Ò,äØØ˜~Ð.Ø"×-Ñ-ôð ð ×.Ñ.¨w¸×8KÑ8KÓLˆØ×Ò D×$:Ñ$:¸8ÀU×EUÑEUÔ$VÜØØÐ4Ð5Ø&¨Ð-Ø"×-Ñ-ô	ð ð	Zð #Ø"Ø Ø#×8Ñ8¸¿¹ÀWÇ\Á\ÓRñ	ˆGð )˜5Ÿ=™=Ñ3¨7Ñ3×3ˆFÜ˜&¤+Ô.Ø$+×$6Ñ$6�Ô!Ø�Ü¨3°VÈ×HZÑHZÔ[Ð[ðY NùðP 4ùô
 ò 	Zô ×ÑÐ6¸¿¹×8LÑ8LÈgÏlÉlÔ[à0Ø")×"4Ñ"4ñ$ˆDð �{Š{ä!$ S£��X‘Ü¨3°TÀg×FXÑFXÔYÕYûð	Züsc   ‚BIÂGÂB*IÅA G
 ÆGÆ&G
 Æ,IÆ-G
 ÇIÇG
 Ç
	IÇA5IÉIÉ	IÉIÉIc                ó*   — |rd|› �S |rd|› �S t         S )u‰  Derive the rate-limit bucket key for a request.

        The rule lives in one place so it is auditable: the authenticated
        principal wins, the caller-supplied address is next, and a request that
        offers neither is attributed to a single shared ``unidentified`` bucket.
        Every key is namespaced by its kind so a principal named like an
        address (or vice versa) can never share quota with it.

        Security rationale: this helper is deliberately total â€” it never
        returns ``None`` and never reports "no key". The previous inline
        ``user_id or request.client_addr`` guard short-circuited the rate-limit
        check when both were absent, so an unauthenticated request (the
        ``allow_unauthenticated=True`` path) was served with no bucket at all
        and no limit could ever fire: fail-open, and fail-open precisely on the
        floodable path.

        Trade-off, chosen deliberately: the shared bucket means one unidentified
        caller can consume the unidentified quota and lock out other unidentified
        callers for the rest of the window (a denial-of-service on that bucket
        only). The alternative â€” having no bucket for such requests â€” lets one
        caller exhaust the process itself, and that limit is unbounded and
        permanent for the lifetime of the gateway. Bounding the damage to a
        shared quota of the anonymous path is the smaller and reversible failure;
        authenticated principals keep their own independent buckets and are
        unaffected either way.

        Args:
            user_id: Authenticated principal id, or ``None`` when the request is
                anonymous or unauthenticated.
            client_addr: Caller-supplied remote address, or ``None`` when the
                transport did not supply one.

        Returns:
            A non-empty, namespaced bucket key, always safe to use.
        z
principal:zaddr:)ÚUNIDENTIFIED_RATE_LIMIT_KEY)rD   r   r6   s      r   r„   z!APIGateway._derive_rate_limit_key#  s+   € ñH Ø ˜yÐ)Ð)ÙØ˜;˜-Ð(Ð(Ü*Ð*r   c                ó
  — t        j                   «       }|dz
  }|| j                  vrg | j                  |<   | j                  |   }|D �cg c]
  }||kD  sŒ	|‘Œ c}|dd t        |«      |k\  ry|j                  |«       yc c}w )ab  Simple in-memory sliding window rate limiter.

        ``key`` MUST be a stable bucket identifier produced by
        ``_derive_rate_limit_key`` (a principal, a client address, or the shared
        ``unidentified`` bucket). Keying on a fresh per-request UUID would
        create a new empty bucket every call and the limit could never trigger.
        é<   NFT)ÚtimerS   rl   Úappend)rD   ÚkeyÚlimitÚnowÚwindow_startÚbucketÚtss          r   r…   zAPIGateway._check_rate_limitM  s„   € ô �i‰i‹kˆØ˜R‘xˆØ�d×.Ñ.Ñ.Ø,.ˆD×$Ñ$ SÑ)Ø×)Ñ)¨#Ñ.ˆà"(Ö>˜B¨B°Ó,=’RÒ>ˆ‰qˆ	Üˆv‹;˜%ÒØØ�‰�cÔØùò	 ?s   Á

B ÁB c                óH   — t        | j                  j                  «       «      S )zList all registered routes.)r-   rN   Úvalues)rD   s    r   Úlist_routeszAPIGateway.list_routesa  s   € ä�D—L‘L×'Ñ'Ó)Ó*Ð*r   c           
     óŒ  — i }| j                   j                  «       D ]•  }|j                  |vri ||j                  <   |j                  |j                  |j
                  |j                  rdg igng ddiddiddiddidœdœ||j                     |j                  j                  j                  «       <   Œ— d	||d
œ|dddddœiidœS )züGenerate OpenAPI 3.0 schema.

        The schema reflects only route metadata (summary, tags, security
        requirements). It never embeds credentials, API keys, or handler
        claims, so it is safe to expose publicly for documentation.
        Ú
bearerAuthÚdescriptionÚSuccessr}   zRate limitedzInternal error)Ú200Ú401Ú429Ú500)r#   ÚoperationIdr&   ÚsecurityÚ	responsesz3.0.0)ÚtitleÚversionÚsecuritySchemesÚhttpÚbearer)ÚtypeÚscheme)Úopenapir`   ÚpathsÚ
components)
rN   r›   r   r#   r!   r&   r(   r   r[   r\   )rD   r¨   r©   r°   rb   s        r   Úgenerate_openapizAPIGateway.generate_openapie  sè   € ð !#ˆØ—\‘\×(Ñ(Ó*ò 	ˆEØ�z‰z Ñ&Ø$&��e—j‘jÑ!à Ÿ=™=Ø$Ÿz™zØŸ
™
Ø49×4GÒ4G˜l¨BÐ/Ñ0ÈRà)¨9Ð5Ø)¨>Ð:Ø)¨>Ð:Ø)Ð+;Ð<ñ	ñ=ˆE�%—*‘*Ñ˜eŸl™l×0Ñ0×6Ñ6Ó8Ò9ð	ð  Ø#°Ñ8Øà!Ø ¨6¸XÑ"Fð$ðñ		
ð 		
r   )Nr‘   )
rT   zAuthProvider | NonerU   r?   rK   r'   rL   r'   rH   ÚNone)r   r   r   r   r    r   r!   r5   r#   r   r&   zlist[str] | Noner(   r'   r*   r)   rH   r   )r   r   r   r   rH   zRoute | None)ro   r   r   r   rH   r'   )ro   r   r   r   rH   r0   )rE   r/   rH   r>   )r   r5   r6   r5   rH   r   )r”   r   r•   r?   rH   r'   )rH   zlist[Route])zCyberBrain26 APIz1.0.0)r¨   r   r©   r   rH   zdict[str, Any])r   r   r   r+   rV   rc   rb   rg   rz   r�   r„   r…   rœ   r²   r   r   r   rJ   rJ   \   sì   „ ñðB .2Ø"$ð>ð
 ',Øñ>à*ð>ð  ð>ð
  $ð>ð ð>ð 
ó>ð2  ØØ!%Ø"Ø!%ñàðð ðð /ð	ð ðð ðð ðð ðð ðð 
óó8óó&	óJZóX(+óTó(+õ 
r   rJ   c                  ó"   — e Zd ZdZddd„Zdd„Zy)ÚSimpleAPIKeyAutha„  Simple API key (bearer token) authentication for development.

    Security properties:

    * Plaintext API keys are never retained. The constructor hashes each key
      with SHA-256 and stores only the digest, so a memory/core dump or an
      accidental log of the provider cannot disclose live credentials.
    * Lookup is performed with ``hmac.compare_digest`` instead of a plaintext
      dict membership test, avoiding the timing side-channel of a plain
      ``dict.get`` which could leak information about which keys are valid.
    * Returned ``claims`` never contain the raw bearer token, preventing
      credential leakage into handler logs or response bodies.

    Known gaps (documented, intentionally not implemented): keys never expire
    and there is no rotation support. Production deployments should use a
    dedicated auth service with short-lived tokens and key rollover.
    Nc           	     óÊ   — |xs i j                  «       D ��ci c]8  \  }}t        j                  |j                  d«      «      j	                  «       |“Œ: c}}| _        yc c}}w )zÕBuild a provider from a plaintext ``api_key -> user_id`` mapping.

        Keys are hashed on construction and the plaintext input is discarded;
        the provider holds only SHA-256 digests internally.
        úutf-8N)rf   ÚhashlibÚsha256ÚencodeÚdigestÚ_keys)rD   Ú
valid_keysÚapi_keyr   s       r   rV   zSimpleAPIKeyAuth.__init__œ  sU   € ð &0Ò%5°2×$<Ñ$<Ó$>÷(
á �˜ô �N‰N˜7Ÿ>™>¨'Ó2Ó3×:Ñ:Ó<¸gÑEó(
ˆ�
ùó (
s   ˜=Ac              ƒ  ón  K  — |j                   j                  dd«      }|j                  d«      sd i fS |dd  }t        j                  |j                  d«      «      j                  «       }d }| j                  j                  «       D ]  \  }}t        j                  ||«      sŒ|} n |�|d|ifS d i fS ­w)NÚauthorizationr"   zBearer é   r·   Úsub)r1   Úgetrn   r¸   r¹   rº   r»   r¼   rf   ÚhmacÚcompare_digest)rD   rE   Úauth_headerr¾   r»   r   Ústored_digestÚuids           r   rF   zSimpleAPIKeyAuth.authenticate§  s¿   è ø€ Ø—o‘o×)Ñ)¨/¸2Ó>ˆØ×%Ñ% iÔ0Ø˜�8ˆOØ˜a˜b�/ˆÜ—‘ §¡¨wÓ 7Ó8×?Ñ?ÓAˆØ"ˆØ"&§*¡*×"2Ñ"2Ó"4ò 	ÑˆM˜3Ü×"Ñ" 6¨=Õ9Ø�Ùð	ð Ðð ˜U GÐ,Ð,Ð,Ø�Rˆxˆùs   ‚B!B5Â$B5r8   )r½   zdict[str, str] | NonerH   r³   rG   )r   r   r   r+   rV   rF   r   r   r   rµ   rµ   ‰  s   „ ñô$	
ôr   rµ   )r�   rJ   r/   r>   rB   r   r   rµ   )!r+   Ú
__future__r   r¸   rÄ   Úloggingr’   Úcollections.abcr   r   Údataclassesr   r   Úenumr   Útypingr	   r
   Úuuidr   Ú	getLoggerr   r_   r�   r   r   r   r/   r>   rB   rJ   rµ   Ú__all__r   r   r   ú<module>rÒ      sË   ðñõ #ã Û Û Û ß /ß (Ý ß  Ý à	ˆ×	Ñ	˜8Ó	$€ð -Ð ô��dô ð ÷	"ð 	"ó ð	"ð ÷Að Aó ðAð& ÷ð ó ðô�8ô ÷i
ñ i
÷Z	-ñ -ò`	�r   