
    (Gj,                        d Z ddlmZ ddlZddlmZ d7dZd8d	Zd9dZd:dZ	d;dZ
d<dZd7dZd=dZd7dZd7dZd>dZd>dZd?d@dZdddddAd)ZdBd+Zdd,dCd1ZdBd2ZdDdEd6ZdS )Fu  Relay/connector support package for the Hermes gateway.

EXPERIMENTAL. This package implements the gateway side of the "Gateway Gateway"
relay design: a generic ``RelayAdapter`` plus the wire-serializable
``CapabilityDescriptor`` the connector hands it at handshake time, and the
production ``WebSocketRelayTransport`` that dials the connector. The public API
(module names, descriptor field set, transport protocol) MAY CHANGE without a
deprecation cycle until at least two real Class-1 platforms (Discord + Telegram)
have shaken out the schema.

See ``docs/relay-connector-contract.md`` for the formal cross-repo interface.

Activation is driven by configuration, not a separate feature flag: the relay
platform is registered when a connector relay URL is configured
(``GATEWAY_RELAY_URL`` env or ``gateway.relay_url`` in config.yaml). Deployments
that don't set it are unaffected — exactly the same shape as ``gateway.proxy_url``.
    )annotationsN)OptionalreturnOptional[str]c                    t           j                            dd                                          } | r|                     d          S 	 ddlm}  |            }|                    d          pi                     dd                                          } | r|                     d          S n# t          $ r Y nw xY wdS )	a%  The connector relay endpoint URL, or None when relay is not configured.

    Checks ``GATEWAY_RELAY_URL`` (convenient for Docker) first, then
    ``gateway.relay_url`` in config.yaml. A non-empty value activates the relay
    platform; absence means a normal direct/single-tenant gateway.
    GATEWAY_RELAY_URL /r   _load_gateway_configgateway	relay_urlN)osenvirongetstriprstripgateway.runr   	Exceptionurlr   cfgs      </home/rurouni/.hermes/hermes-agent/gateway/relay/__init__.pyr   r      s     *..,b
1
1
7
7
9
9C
 zz#444444""$$wwy!!'R,,["==CCEE 	#::c??"	#   4s   A#B0 0
B=<B=list[tuple[str, str]]c                    t           j                            dd                                          } d |                     d          D             }|sdgS t                      }g }|D ]|}|                    |          pi }t          |t                    r5t          |                    dd                                                    nd}|	                    ||f           }|S )uf  The (platform, bot_id) pairs this gateway fronts over the relay (Phase 1.5).

    Shape A (multi-platform-per-agent, D-Q1.5c — CUT OVER, no scalar fallback):
    one gateway fronts a SET of platforms on one WS connection. The set is the
    env-stamped deploy config:

      - ``GATEWAY_RELAY_PLATFORMS`` — comma-sep list (e.g. ``discord,telegram``).
      - ``GATEWAY_RELAY_BOT_IDS`` — JSON keyed map
        ``{"discord": {"botId": "..."}, "telegram": {"botId": "...", "username": "..."}}``.

    Returns the ordered list of ``(platform, bot_id)`` pairs (the FIRST is the
    default the handshake/descriptor falls back to). The connector accepts N
    hellos accumulating into its advertised set; outbound frames discriminate
    per-frame on the platform (gateway-gateway D-Q1.5b.1). A platform present in
    the list but absent from the ids map resolves with an empty bot_id (the
    connector rejects an unprovisioned platform with a structured failure).

    Defaults to ``[("relay", "")]`` when nothing is configured (the generic
    single-plane fallback for a connector that didn't stamp a platform set).
    GATEWAY_RELAY_PLATFORMSr	   c                ^    g | ]*}|                                 |                                 +S  r   ).0ps     r   
<listcomp>z-relay_platform_identities.<locals>.<listcomp>E   s-    JJJq		JJJJ    ,)relayr	   botId)
r   r   r   r   split_relay_bot_ids_map
isinstancedictstrappend)platforms_raw	platformsidsoutplatformentrybot_ids          r   relay_platform_identitiesr4   /   s    * JNN#<bAAGGIIMJJM$7$7$<$<JJJI 


C!#C ' '!!'R8B5$8O8OWUYYw++,,22444UW

Hf%&&&&Jr#   r*   c                 P   ddl } ddl}t          j                            dd                                          }|si S 	 |                     |          }t          |t                    r|ni S # t          $ r- |
                    d                              d           i cY S w xY w)u   Parse ``GATEWAY_RELAY_BOT_IDS`` (JSON keyed map). Never raises — a malformed
    map yields ``{}`` so a bad config degrades to empty bot ids (the connector
    rejects an unprovisioned platform) rather than crashing boot.r   NGATEWAY_RELAY_BOT_IDSr	   gateway.relayz:GATEWAY_RELAY_BOT_IDS is not valid JSON; treating as empty)jsonloggingr   r   r   r   loadsr)   r*   r   	getLoggerwarning)r8   r9   rawparseds       r   r(   r(   Q   s     KKKNNN
*..0"
5
5
;
;
=
=C 	C#FD119vvr9   /**22H	
 	
 	
 				s    -A. .4B%$B%r1   r+   c                    t                                          |           }t          |t                    r9|                    d          }|r"t	          |                              d          S dS )zThe bot's deep-link username/handle for a platform (e.g. Telegram's
    ``@handle`` for ``t.me/<handle>``), read from the per-platform entry in
    ``GATEWAY_RELAY_BOT_IDS``. None when absent (most platforms don't need one).
    username@N)r(   r   r)   r*   r+   lstrip)r1   r2   r@   s      r   relay_bot_usernamerC   e   sg    
   $$X..E% -99Z(( 	-x=='',,,4r#   tuple[str, str]c                 *    t                      d         S )u1  The PRIMARY (platform, bot_id) — the first identity in the configured set.

    Kept for call sites that need a single representative identity (the default
    descriptor platform, the policy projection's primary). The full set is
    ``relay_platform_identities()``. Defaults to ``("relay", "")``.
    r   )r4   r   r#   r   relay_platform_identityrF   r   s     %&&q))r#   #tuple[Optional[str], Optional[str]]c                 4   t           j                            dd                                          } t           j                            dd                                          }| r|s	 ddlm}  |                                d          pi }| p6t          |                    dd          pd                                          } |p6t          |                    dd          pd                                          }n# t          $ r Y nw xY w| pd	|pd	fS )
a  The (gateway_id, upgrade_secret) this gateway authenticates the WS upgrade with.

    Both come from enrollment (``hermes gateway enroll`` writes them to
    ``~/.hermes/.env``): ``GATEWAY_RELAY_ID`` identifies the enrolled instance,
    ``GATEWAY_RELAY_SECRET`` is the per-gateway signing secret. Either absent ->
    ``(None, None)`` and the transport dials unauthenticated (dev/test, or a
    connector that doesn't enforce auth). Checks env first (Docker), then
    ``gateway.relay_id`` / ``gateway.relay_secret`` in config.yaml.
    GATEWAY_RELAY_IDr	   GATEWAY_RELAY_SECRETr   r   r   relay_idrelay_secretNr   r   r   r   r   r   r+   r   )
gateway_idsecretr   r   s       r   relay_connection_authrP   |   s'     2B77==??JZ^^2B77==??F 6 	888888''))--i88>BC#Qs377:r+B+B+Hb'I'I'O'O'Q'QJMs377>2#>#>#D"EEKKMMFF 	 	 	D	$$//s   *BD 
DDc                 x   t           j                            dd                                          } | sp	 ddlm}  |                                d          pi }t          |                    dd          pd                                          } n# t          $ r d} Y nw xY w|                     d          pdS )	a  The gateway's own PUBLIC inbound URL, asserted to the connector at provision.

    The connector delivers signed inbound POSTs to this URL and stores it on the
    tenant's route rows. It is gateway-asserted (the connector scopes it to the
    verified tenant, so a dishonest gateway can only misdirect its OWN inbound).
    The *source* of the value differs by deployment but the code path is uniform:
    a self-hosted operator sets ``GATEWAY_RELAY_ENDPOINT`` (mirrors how they set
    ``HERMES_DASHBOARD_PUBLIC_URL``); a hosted/NAS container has the same var
    stamped in (NAS knows the public URL only in that case). Absent -> the
    gateway provisions outbound-only (no inbound routes written).

    Env first (Docker), then ``gateway.relay_endpoint`` in config.yaml.
    GATEWAY_RELAY_ENDPOINTr	   r   r   r   relay_endpointr
   N	r   r   r   r   r   r   r+   r   r   r   s      r   rS   rS      s     *..12
6
6
<
<
>
>C 	888888''))--i88>BCcgg.339r::@@BBCC 	 	 	CCC	::c??"d"   AB B"!B"	list[str]c                    t           j                            dd                                          } | s	 ddlm}  |                                d          pi }|                    dd          }t          |t          t          f          rd |D             S t          |pd                                          } n# t          $ r d} Y nw xY wd |                     d	          D             S )
a  Discriminators (scope_ids / chat_ids / paths) this gateway's tenant owns.

    Gateway-provided config, paired with ``relay_endpoint()``: the connector
    writes one route row per (routeKey -> tenant, endpoint), so route keys only
    take effect alongside an endpoint. Empty -> outbound-only provisioning (the
    connector accepts an empty set and writes no route rows).

    ``GATEWAY_RELAY_ROUTE_KEYS`` is comma-separated; config.yaml
    ``gateway.relay_route_keys`` may be a list or a comma string.
    GATEWAY_RELAY_ROUTE_KEYSr	   r   r   r   relay_route_keysc                    g | ]D}t          |                                          #t          |                                          ES r   r+   r   r    ks     r   r"   z$relay_route_keys.<locals>.<listcomp>   s9    FFF1s1vv||~~FAFFFr#   c                ^    g | ]*}|                                 |                                 +S r   r   r\   s     r   r"   z$relay_route_keys.<locals>.<listcomp>   s-    ;;;!;AGGII;;;r#   r$   )r   r   r   r   r   r   r)   listtupler+   r   r'   )r=   r   r   vals       r   rY   rY      s    *..3R
8
8
>
>
@
@C 
		888888''))--i88>BC'',b11C#e}-- GFFFFFFciR..&&((CC 	 	 	CCC	;;syy~~;;;;s   A"B= #B= =CCc                 R   t           j                            dd                                          } | sp	 ddlm}  |                                d          pi }t          |                    dd          pd                                          } n# t          $ r d} Y nw xY w| pdS )u_  Stable per-instance id this gateway forwards at provision (Phase 6 Unit α).

    Binds the connector's ``gatewayId -> instanceId`` so the connector can route
    inbound per-instance (not tenant-broadcast) once Phase 6 delivery lands. The
    value is the NAS ``AgentInstance.id`` for a managed agent (NAS stamps
    ``GATEWAY_RELAY_INSTANCE_ID`` into the container env, beside
    ``GATEWAY_RELAY_URL``); a self-hosted operator may set it explicitly. It is
    gateway-asserted but safely scoped: the org/tenant stays token-verified, so a
    dishonest gateway can only bind ITS OWN tenant's instance — the same posture
    as ``relay_endpoint()``. Absent -> the connector stores null and per-instance
    routing simply has no binding for this connection yet (back-compat).

    Env first (Docker/NAS), then ``gateway.relay_instance_id`` in config.yaml.
    GATEWAY_RELAY_INSTANCE_IDr	   r   r   r   relay_instance_idNrM   valuer   r   s      r   rd   rd      s     JNN6;;AACCE 	888888''))--i88>BC 3R88>B??EEGGEE 	 	 	EEE	=DrU   c                 x   t           j                            dd                                          } | sp	 ddlm}  |                                d          pi }t          |                    dd          pd                                          } n# t          $ r d} Y nw xY w|                     d          pdS )	uf  The gateway's WAKE URL, forwarded at provision (Phase 5 §5.2 wake PRIMITIVE).

    A poke target the connector issues a payload-free GET to when a buffered-only
    (going-idle) destination for this instance receives its first buffered event,
    so a suspended gateway wakes, reconnects its relay WS, and drains its
    delivery-leg backlog. The value's *source* differs by deployment but the code
    path is uniform: a managed/NAS container has ``GATEWAY_RELAY_WAKE_URL`` stamped
    in (NAS knows the Fly autostart / dashboard hostname); a self-hosted operator
    sets it explicitly (or passes ``--wake-url`` to ``hermes gateway enroll``).

    Gateway-asserted but safely scoped: the org/tenant stays token-verified, so a
    dishonest gateway can only register a wake target for ITS OWN instance — the
    same posture as ``relay_instance_id()`` / the retired ``relay_endpoint()``.
    Absent -> the connector stores null and simply can't wake this instance
    (buffering still works; the gateway drains whenever it next reconnects).

    Env first (Docker/NAS), then ``gateway.relay_wake_url`` in config.yaml.
    GATEWAY_RELAY_WAKE_URLr	   r   r   r   relay_wake_urlr
   NrT   re   s      r   ri   ri      s    & JNN3R88>>@@E 	888888''))--i88>BC 0"55;<<BBDDEE 	 	 	EEE	<<$$rU   relay_dial_urlc                N   |                      d          }|                    d          rd|t          d          d         z   }n/|                    d          rd|t          d          d         z   }|                    d          r|dt          d                    }| dS )	uY   Map the ``ws(s)://…/relay`` dial URL to the ``http(s)://…/relay/provision`` POST URL.r
   ws://http://Nwss://https:///relayz/relay/provisionr   
startswithlenendswithrj   r=   s     r   _provision_urlrv     s    



$
$C
~~g /#c'llmm,,		!	! /3s8}}~~..
||H $"S]]N"#####r#   c                N   |                      d          }|                    d          rd|t          d          d         z   }n/|                    d          rd|t          d          d         z   }|                    d          r|dt          d                    }| dS )	u   Map the ``ws(s)://…/relay`` dial URL to the ``http(s)://…/relay/policy`` POST URL.

    Same host derivation as ``_provision_url``; the connector mounts the
    relevance-policy update channel at ``/relay/policy`` (Phase 6 Unit ζ).
    r
   rl   rm   Nrn   ro   rp   z/relay/policyrq   ru   s     r   _policy_urlrx     s     


$
$C
~~g /#c'llmm,,		!	! /3s8}}~~..
||H $"S]]N"#    r#   Optional[dict]c                   | t                      \  } }| r| dk    rdS d}g }	 ddlm}  |            pi }|                    |           }t	          |t
                    s?|                    d          pi                     d          pi                     |           }t	          |t
                    s*|                    d          pi                     |           }t	          |t
                    r|ni }d|v r|                    d          }n*|                    d          |                    d          }|                    d          }||                    d          }t	          |t          t          f          rd	 |D             }nHt	          |t                    r3|	                                rd
 |
                    d          D             }n# t          $ r Y nw xY wt          j                            |                                  dd                                          	                                }|dv }	|t!          |          nd}
|
s|s|	sdS | |
||	dS )u  Project a fronted platform's RELEVANCE config into the connector's generic vocabulary.

    The connector's relevance gate (Phase 6 Unit ζ) reasons over a
    platform-agnostic policy — ``requireAddress`` / ``freeResponseScopes`` /
    ``allowOtherBots`` — NOT over Discord/Telegram words. This is the gateway
    side of that contract: it reads the agent's existing relevance knobs and
    emits the generic shape the connector stores per-instance (Phase 1.5: the
    connector keys the policy by ``(tenant, platform, instanceId)``, so each
    fronted platform gets its own row — pass its name here).

    Mapping (the connector vocabulary ← the gateway's existing config):
      - ``requireAddress``     ← the platform's ``require_mention`` (the agent
        only engages a non-owner message that @mentions it / replies to it).
      - ``freeResponseScopes`` ← the platform's ``free_response_channels`` (the
        channel/scope ids where ``require_mention`` is waived — same scope
        vocabulary the connector's δ scope grants + ε floor use).
      - ``allowOtherBots``     ← ``{PLATFORM}_ALLOW_BOTS`` in {"mentions","all"}
        (whether bot-authored messages are admitted; default off).

    Read from the relay platform's config block (the platform the connector
    fronts, e.g. ``discord:``), falling back to the bridged top-level keys, then
    the ``{PLATFORM}_*`` env. ``platform`` defaults to the PRIMARY fronted
    platform (back-compat). Returns the generic dict, or None when relay isn't
    configured or the platform exposes no relevance knobs (⇒ the connector's
    quiet default already matches, so there's nothing to declare).
    Nr%   r   r   r   r.   require_mentionfree_response_channelsc                    g | ]D}t          |                                          #t          |                                          ES r   r[   r    cs     r   r"   z*relay_relevance_policy.<locals>.<listcomp>V  s9    KKKCFFLLNNKSVV\\^^KKKr#   c                ^    g | ]*}|                                 |                                 +S r   r   r~   s     r   r"   z*relay_relevance_policy.<locals>.<listcomp>X  s-    LLL1!''))LQWWYYLLLr#   r$   _ALLOW_BOTSr	   >   allmentionsF)r1   requireAddressfreeResponseScopesallowOtherBots)rF   r   r   r   r)   r*   r_   r`   r+   r   r'   r   r   r   upperlowerbool)r1   _bot_idr{   free_responser   r   plat_cfgfrcallow_bots_envallow_other_botsrequire_addresss              r   relay_relevance_policyr     s   6 355' x7**t O!M444444""$$*778$$(D)) 	Y++1r66{CCIrNNxXXH(D)) 	B,,277AAH)(D99A88r((&ll+<==OOWW&''3!gg&788Oll344;''233CcD%=)) 	MKKSKKKMMS!! 	Mciikk 	MLL		#LLLM   
 Z^^x~~'7'7$D$D$DbIIOOQQWWYYN%)<</>/Jd?+++PUO  = 9I t )+*	  s   F4G 
G%$G%g      .@)instance_idwake_urltimeoutprovision_urlaccess_tokenrN   r3   gateway_endpoint
route_keysr   r   r   floatc        
   	        ddl }
ddl}ddl}||||pd|d}|r||d<   |r||d<   |
                    |                              d          }|j                            | |dd	| d
d
d          }	 |j                            ||	          5 }|
                    |	                                
                                          }ddd           n# 1 swxY w Y   n# |j        j        $ r}d}	 |
                    |	                                
                                          pi                     dd          }n# t          $ r Y nw xY wt          d|j         |rd| ndz             |d}~w|j        j        $ r}t          d|j                   |d}~ww xY wt'          |t(                    r|                    d          st          d          |S )a  POST to the connector's ``/relay/provision`` and return the JSON body.

    The connector validates ``access_token`` against NAS, derives the
    authoritative tenant, mints the per-gateway secret + per-tenant delivery key,
    upserts the tenant's route rows, and returns
    ``{secret, deliveryKey, tenant, gatewayId, routeKeys}``. Raises RuntimeError
    with a user-facing message on any non-2xx / transport failure.
    r   Nr	   )	gatewayIdr1   r&   gatewayEndpoint	routeKeys
instanceIdwakeUrlutf-8POSTBearer application/jsonAuthorizationzContent-TypeAcceptdatamethodheadersr   errorzconnector returned HTTP z: could not reach connector: rO   z5connector returned an unexpected response (no secret))r8   urllib.errorurllib.requestdumpsencoderequestRequesturlopenr:   readdecoder   	HTTPErrorr   r   RuntimeErrorcodeURLErrorreasonr)   r*   )r   r   rN   r1   r3   r   r   r   r   r   r8   urllibbodyr   reqresppayloadexcdetails                      r   _post_provisionr   p  s   * KKK  +1r D  )(\  #"Y::d""7++D
.
 
 5|55.(
 
	 ! 	 	CP^##C#99 	7Tjj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7<!   	jj!2!2!4!455;@@"MMFF 	 	 	D	1sx11f5T]&]]]RTU
 
	 <  P P PEEEFFCOP gt$$ TGKK,A,A TRSSSNsm   3C! :C	C! CC! CC! !F$0E53AEE5
EE5E%E55F$FF$r   c                    ddl } |                     d          }t                      }|sdS t                      \  }}|r|r|                    d           dS 	 ddlm}  |            }n.# t          $ r!}|                    d|           Y d}~dS d}~ww xY wt                      }ddl
}		 |	                                                                }
n# t          $ r d}
Y nw xY wt          j                            d	d                                          pd
|
pd }t!                      }t#                      }t%                      }t'                      }g }i }|D ]0\  }}	 t)          t+          |          ||||||||	  	        }n.# t,          $ r!}|                    d||           Y d}~Sd}~ww xY w|                    |           dt          j        vst          j                            d          st1          |                    d          p|          t          j        d	<   t1          |                    d          pd          t          j        d<   t1          |                    d          pd          t          j        d<   2|s5|                    dd                    d |D                                  dS t1          |                    d          pd          }|                    dt          j                            d	|          |pdd                    |          t5          |          |rdnd|pd|rdnd           dS )uz  Boot-time relay self-provision: mint relay creds in-process, no human, no disk.

    Fires when relay is configured (``relay_url()`` set) and NO per-gateway secret
    is already present, AND the agent can resolve its own Nous access token. In
    that case the runtime resolves the agent's own Nous access token (the same
    ``resolve_nous_access_token()`` the enroll CLI / dashboard register use),
    POSTs ``/relay/provision`` asserting its own endpoint + route keys, and sets
    ``GATEWAY_RELAY_ID`` / ``GATEWAY_RELAY_SECRET`` / ``GATEWAY_RELAY_DELIVERY_KEY``
    into ``os.environ`` so the subsequent ``register_relay_adapter()`` picks them
    up. The creds live ONLY in process memory — never written to ``~/.hermes/.env``.

    The trigger is deliberately NOT ``is_managed()``: that means
    "package-manager/NixOS-managed" and is False on a NAS-hosted Fly agent (which
    sets neither ``HERMES_MANAGED`` nor a ``.managed`` marker), so gating on it
    blocked the exact hosted case this is for. The real signal is "you pointed me
    at a connector and didn't pin a secret" — which is both NAS-independent and
    self-guarding:

      - A NAS-hosted agent: has ``GATEWAY_RELAY_URL``, no pinned secret, and a
        bootstrapped NAS token -> self-provisions.
      - A self-hosted operator who ran ``hermes gateway enroll``: has a PINNED
        ``GATEWAY_RELAY_SECRET`` -> skipped (the secret-present guard below).
      - A self-hosted box with a relay URL but no NAS identity:
        ``resolve_nous_access_token()`` fails -> graceful no-op.

    Stateless: process-env creds don't survive a restart, so a hosted container
    re-provisions every boot; the connector's rotation window covers a still-
    connected prior instance. An explicitly-pinned ``GATEWAY_RELAY_SECRET`` (env
    or config) is RESPECTED — self-provision skips so an operator pin isn't
    stomped.

    Returns True if it provisioned, False otherwise. NEVER raises: a provision
    failure logs and returns False so the gateway still boots (and
    ``register_relay_adapter`` will simply dial unauthenticated / be rejected,
    rather than the whole gateway crashing).
    r   Nr7   Fz>relay self-provision skipped: GATEWAY_RELAY_SECRET already set)resolve_nous_access_tokenz?relay self-provision skipped: could not resolve Nous token (%s)r	   rI   zgw-hermes)	r   r   rN   r1   r3   r   r   r   r   zJrelay self-provision failed for platform=%s (%s); continuing with the restrJ   r   rO   deliveryKeyGATEWAY_RELAY_DELIVERY_KEYzXrelay self-provision failed for ALL platforms (%s); gateway will boot without relay authr$   c              3      K   | ]	\  }}|V  
d S Nr   )r    r!   _s      r   	<genexpr>z'self_provision_relay.<locals>.<genexpr>0  s&      ..41aQ......r#   tenantzfrelay self-provisioned (gateway_id=%s tenant=%s platforms=%s routes=%d inbound=%s instance=%s wake=%s)?yeszoutbound-onlyunboundnoneT)r9   r;   r   rP   infohermes_cli.authr   r   r<   r4   socketgethostnamer   r   r   r   rS   rY   rd   ri   r   rv   r   r,   r+   joinrs   )r9   loggerdial_urlexisting_idexisting_secretr   r   r   
identitiesr   hostrN   endpointr   r   r   provisionedresultr1   r3   r   s                        r   self_provision_relayr     s   J NNN//F{{H u $9#:#: K  TUUUu======0022    	XZ]^^^uuuuu	 +,,JMMM!!##))++    2B77==??[C[IYQYC[C[JH!##J#%%KH  KF& \ \&	$,X66)%!!)%'!
 
 
FF  	 	 	NN\  
 HHHH	 	8$$$ "332:>>J`;a;a3-0K1H1H1VJ-W-WBJ)*14VZZ5I5I5OR1P1PBJ-.7:6::m;T;T;ZXZ7[7[BJ34 fHH..:.....	
 	
 	
 uH%%+,,F
KKp

):66#J. y%v	 	 	 4sB   A* *
B4BB+&C C! C!"%F
F3F..F3r   
policy_urltokenpolicyintc           	        ddl }ddl}ddl}|                    |                              d          }|j                            | |dd| ddd          }	 |j                            ||	          5 }t          |j	                  cddd           S # 1 swxY w Y   dS # |j
        j        $ r}	t          |	j                  cY d}	~	S d}	~	w|j
        j        $ r}	t          d
|	j                   |	d}	~	ww xY w)u  POST the relevance policy to the connector's ``/relay/policy``; return the HTTP status.

    Authenticated with the gateway's own per-gateway upgrade token (the SAME
    bearer shape as the WS upgrade — ``make_upgrade_token``), so the connector
    resolves ``{tenant, instanceId}`` from its stored secret record, never the
    body. Raises RuntimeError on transport failure (the caller treats any
    failure as non-fatal — relevance is an optimization, not a boot dependency).
    r   Nr   r   r   r   r   r   r   r   )r8   r   r   r   r   r   r   r   r   statusr   r   r   r   r   r   )
r   r   r   r   r8   r   r   r   r   r   s
             r   _post_policyr   B  s    KKK::f$$W--D
.
 
 .u...(
 
	 ! 	 	CP^##C#99 	$Tt{##	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$<!   38}}<  P P PEEEFFCOPsN   B% 7BB% BB% B B% %C<4CC<C<C77C<c                 $   ddl } |                     d          }t                      }|sdS t                      \  }}|r|sdS 	 ddlm}  |||          }n.# t          $ r!}|                    d|           Y d}~dS d}~ww xY wd}t                      D ]\  }	}
t          |	          }|	 t          t          |          ||          }n.# t          $ r!}|                    d|	|           Y d}~^d}~ww xY w|d	k    rwd
}|                    d|                    d          |                    d          t          |                    d          pg           |                    d                     |                    d|	|           |S )uE  Declare this gateway's relevance policy to the connector (Phase 6 Unit ζ).

    Runs at boot AFTER the per-gateway secret is resolved (self-provisioned or
    pinned), projecting the agent's relevance config into the generic vocabulary
    (``relay_relevance_policy``) and POSTing it to ``/relay/policy`` with the
    gateway's own upgrade token. The connector stores it per-instance and the
    relevance gate enforces it on delivery — so the SAME mention-gating /
    free-response / allow-bots behavior the agent applies directly also governs
    relay delivery, and excluded traffic never wakes a scaled-to-zero agent.

    Self-healing: the agent is the source of truth and re-declares every boot
    (mirrors the ``routeKeys`` upsert at provision). Idempotent — a full replace.

    NEVER raises and NEVER blocks boot: relevance is an optimization layered on
    the δ/ε authorization gate (which already protects isolation), so a failed
    declaration just means the connector keeps the prior/quiet policy. Returns
    True iff the connector accepted the policy (HTTP 200).
    r   Nr7   F)make_upgrade_tokenzQrelay policy declaration failed to build token (%s); connector keeps prior policy)r   r   r   z@relay policy declaration failed for platform=%s (%s); continuing   TzSrelay policy declared (platform=%s require_address=%s free_scopes=%d allow_bots=%s)r1   r   r   r   z_relay policy declaration for platform=%s returned HTTP %s; connector keeps prior/default policy)r9   r;   r   rP   gateway.relay.authr   r   r<   r4   r   r   rx   r   r   rs   )r9   r   r   rN   rO   r   r   r   any_declaredr1   r   r   r   s                r   send_relay_policyr   c  s   & NNN//F{{H u.00J V  u999999"":v66   jlopppuuuuu L688  ''11> 	![-B-B%X^___FF 	 	 	NNRT\^a   HHHH		
 S==LKKe

:&&

+,,FJJ344:;;

+,,    NNq   
 s0   A 
B!A==B-C
C8C33C8Fforcer   c           
        	
 ||nt                      
| s
sdS ddlm}m} ddlm ddlmm t                      \  		
fd}|
                     |dd	|d
 dd                     dS )u;  Register the generic ``relay`` platform via the platform registry.

    Registers when a relay URL is configured (or ``force=True`` for tests, which
    builds a transport-less adapter — the unit-test posture). Returns True if
    registration happened. Additive: uses the same registry path as plugin
    adapters, so no core dispatch changes are needed.

    When a URL is present the factory builds a live ``WebSocketRelayTransport``;
    the ``RelayAdapter`` negotiates the real ``CapabilityDescriptor`` at
    ``connect()`` time via ``transport.handshake()``.
    NFr   )PlatformEntryplatform_registry)RelayAdapter)CONTRACT_VERSIONCapabilityDescriptorc                     
ddddddd	  	        }d }r5dd	l m} t                      \  }} |
	t                      ||d
          } | ||          S )NRelayi   FTplainchars)	contract_versionr1   labelmax_message_lengthsupports_draft_streamingsupports_editsupports_threadsmarkdown_dialectlen_unitr   )WebSocketRelayTransport)r   rN   upgrade_secret	reconnect)	transport)gateway.relay.ws_transportr  rP   r4   )configplaceholderr  r  rN   r  r   r   r   r3   r1   resolved_urls         r   _factoryz(register_relay_adapter.<locals>._factory  s     +*-#%*"$

 

 

 	 	JJJJJJ)>)@)@&J// 566%-
 !  I$ |FK9EEEEr#   r%   r   c                     dS )NTr   r   r#   r   <lambda>z(register_relay_adapter.<locals>.<lambda>  s    T r#   builtinu   🔌)namer   adapter_factorycheck_fnsourceemojiT)r   gateway.platform_registryr   r   gateway.relay.adapterr   gateway.relay.descriptorr   r   rF   register)r   r   r   r   r	  r   r   r   r3   r1   r  s        @@@@@@r   register_relay_adapterr    s    /33y{{L \ uJJJJJJJJ222222OOOOOOOO.00Hf&F &F &F &F &F &F &F &F &F &FP $!\	
 	
 	
	 	 	 4r#   )r   r   )r   r   )r   r*   )r1   r+   r   r   )r   rD   )r   rG   )r   rV   )rj   r+   r   r+   r   )r1   r   r   ry   )r   r+   r   r+   rN   r+   r1   r+   r3   r+   r   r   r   rV   r   r   r   r   r   r   r   r*   )r   r   )
r   r+   r   r+   r   r*   r   r   r   r   )FN)r   r   r   r   r   r   )__doc__
__future__r   r   typingr   r   r4   r(   rC   rF   rP   rS   rY   rd   ri   rv   rx   r   r   r   r   r   r  r   r#   r   <module>r     s   $ # " " " " " 				         ,   D   (
 
 
 
* * * *0 0 0 00# # # #4< < < <4   6% % % %>	$ 	$ 	$ 	$! ! ! ! O O O O Ov "&"D D D D D DNH H H HV QU P P P P P PBM M M M`H H H H H H Hr#   