
    WiW                       U 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	mZm
Z
 ddlmZmZmZmZmZ ddlmZmZ ddlmZmZmZmZmZ  ej4                  d	      Z G d
 de      Z G d de      Z G d de      Zej@                  dejB                  dejD                  dejF                  diZ$de%d<   dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/ G d d       Z0d%d!Z1 e1       \  Z2Z3Z4d&d"Z5d'd#Z6d(d$Z7y))a  RLM Neo-Cortex -- Module 1: Memory Gateway.

Single entry-point for all memory read/write/search/delete operations.
Routes content to the correct storage tier based on surprise score,
enforces per-tenant quotas, deduplicates identical content, and
emits Prometheus metrics for observability.

Storage backends:
    - PostgreSQL (Elestio): Durable record store for EPISODIC + SEMANTIC
    - Qdrant (Elestio):     Vector index for semantic search
    - Redis (Elestio):      Short-lived WORKING-tier store + quota counters

Tier routing (Story 1.02):
    score < 0.30  -> DISCARD  (not stored anywhere)
    0.30-0.50     -> WORKING  (Redis only, 24h TTL)
    0.50-0.80     -> EPISODIC (PG + Qdrant)
    >= 0.80       -> SEMANTIC (PG + Qdrant + Redis hot cache)

Implements Stories 1.01-1.10.
    )annotationsN)UTCdatetimetimezone)AnyDictListOptionalTuple)UUIDuuid4   )EMBEDDING_DIMCustomerTierMemoryGatewayProtocolMemoryRecord
MemoryTierzcore.rlm.gatewayc                  $     e Zd ZdZd fdZ xZS )QuotaExceededErrorz5Raised when a tenant's daily write quota is exceeded.c                `    || _         || _        || _        t        |   d| d| d|        y )NzQuota exceeded for tenant z: limit=z
, current=)	tenant_idlimitcurrentsuper__init__)selfr   r   r   	__class__s       )/mnt/e/genesis-system/core/rlm/gateway.pyr   zQuotaExceededError.__init__2   sA    "
( 4G:gY0	
    )r   r   r   intr   r    returnNone__name__
__module____qualname____doc__r   __classcell__r   s   @r   r   r   /   s    ?
 
r   r   c                  $     e Zd ZdZd fdZ xZS )PartialDeleteErrorz?Raised when a memory is deleted from some but not all backends.c                L    || _         || _        t        |   d| d|        y )NzPartial delete for memory z: failed backends=)	memory_idbackends_failedr   r   )r   r-   r.   r   s      r   r   zPartialDeleteError.__init__?   s6    ".( 4./1	
r   )r-   strr.   z	List[str]r!   r"   r#   r)   s   @r   r+   r+   <   s    I
 
r   r+   c                      e Zd ZdZy)GatewayNotInitializedErrorz;Raised when gateway methods are called before initialize().N)r$   r%   r&   r'    r   r   r1   r1   H   s    Er   r1   i  i  i'  zDict[CustomerTier, int]DAILY_QUOTAzrlm:quota:{tenant_id}:{date}z#rlm:working:{tenant_id}:{memory_id}zrlm:hot:{tenant_id}:{memory_id}i_ g333333?g      ?g?i    rlm_memoriesc                     e Zd ZdZ	 	 	 	 d	 	 	 	 	 	 	 	 	 ddZedd       ZddZddZ	 d	 	 	 	 	 	 	 	 	 	 	 d dZ		 d!	 	 	 	 	 	 	 d"dZ
	 	 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Z	 	 	 	 	 	 	 	 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Zy)0MemoryGatewaya0  Unified gateway for all RLM Neo-Cortex memory operations.

    Usage::

        gw = MemoryGateway()
        await gw.initialize()
        record = await gw.write_memory(tenant_id, "content", "source", "domain")
        results = await gw.search_memories(tenant_id, "query")
        await gw.close()
    Nc                .   |xs  t         j                  j                  dd      | _        |xs  t         j                  j                  dd      | _        |xs  t         j                  j                  dd      | _        |xs  t         j                  j                  dd      | _        d| _        d| _        d| _	        d| _
        d| _        d| _        t        j                  d| j                  rd	nd
| j                  rd	nd
| j                  rd	       yd
       y)a  Create a MemoryGateway instance.

        All parameters fall back to environment variables if not provided.
        Call ``await gateway.initialize()`` before any operations.

        Args:
            pg_dsn: PostgreSQL DSN.  Falls back to DATABASE_URL.
            qdrant_url: Qdrant REST URL.  Falls back to QDRANT_URL.
            qdrant_api_key: Qdrant API key.  Falls back to QDRANT_API_KEY.
            redis_url: Redis URL.  Falls back to REDIS_URL.
        DATABASE_URL 
QDRANT_URLQDRANT_API_KEY	REDIS_URLNFz2MemoryGateway created (pg=%s, qdrant=%s, redis=%s)
configuredmissing)osenvironget_pg_dsn_qdrant_url_qdrant_api_key
_redis_url_pg_pool_qdrant_redis_initialized	_surprise_ledgerloggerinfo)r   pg_dsn
qdrant_urlqdrant_api_key	redis_urls        r   r   zMemoryGateway.__init__   s    $ C!C%Ib)I-U@PRT1U#Frzz~~k2'F " ! # @ LLLi ,,L) OOL		
 2;		
r   c                    | j                   S )z8True only after a successful call to :meth:`initialize`.)rJ   r   s    r   is_initializedzMemoryGateway.is_initialized   s        r   c                  K   | j                   st        d      | j                  st        d      | j                  st        d      	 ddl}|j                  | j                   ddd	       d{   | _        t        j                  d
       	 ddlm}  || j                  | j                  xs dd      | _        | j                          d{    t        j                  dt                	 ddlm} |j'                  | j                  ddd      | _        | j(                  j+                          d{    t        j                  d       ddlm}  |       | _        ddlm}  || j                   | j                        | _        | j6                  j9                          d{    d| _        t        j                  d       y7 \# t        $ r}t        j                  d|        d}~ww xY w7 &# t        $ r}t        j                  d|        d}~ww xY w7 # t        $ r}t        j                  d|        d}~ww xY w7 w)zOpen connections to PostgreSQL, Qdrant and Redis.

        Raises:
            ValueError: If a required environment variable is missing.
        z9DATABASE_URL environment variable is required but not setz7QDRANT_URL environment variable is required but not setz6REDIS_URL environment variable is required but not setr   N   
      )min_sizemax_sizecommand_timeoutz!PostgreSQL async pool establishedz#Failed to connect to PostgreSQL: %s)AsyncQdrantClient)urlapi_keytimeoutz)Qdrant client initialized (collection=%s)zFailed to initialize Qdrant: %sT   )decode_responsessocket_timeoutsocket_connect_timeoutz"Redis async connection establishedzFailed to connect to Redis: %sr   )SurpriseIntegration)EntitlementLedger)rO   rR   u0   MemoryGateway initialized — all backends ready)rC   
ValueErrorrD   rF   asyncpgcreate_poolrG   rM   rN   	Exceptionerrorqdrant_clientr]   rE   rH   _ensure_qdrant_collection_QDRANT_COLLECTIONredis.asyncioasynciofrom_urlrI   pingsurprisere   rK   entitlementrf   rL   connectrJ   )r   rh   excr]   aioredisre   rf   s          r   
initializezMemoryGateway.initialize   s     ||K  I  H 
	")"5"5 "	 #6 # DM KK;<	7,$$,,4DL 00222KKCEWX	,"++!% '(	 , DK ++""$$$KK<= 	2,.2(<<oo
 ll""$$$ FGk  	LL>D	 3 	LL:C@	 % 	LL93?	 	%s   AI	&G /G0G AG9 G6G9 0AH# 7H!8H# AI)I*!IG 	G3G..G33I6G9 9	HHHI!H# #	I,IIIc                n  K   | j                   *	 | j                   j                          d{    d| _         | j                  *	 | j                  j	                          d{    d| _        | j
                  *	 | j
                  j	                          d{    d| _        | j                  #	 | j                  j	                          d{    d| _        t        j                  d       y7 # t        $ r Y w xY w7 # t        $ r Y w xY w7 y# t        $ r Y w xY w7 T# t        $ r Y ]w xY ww)zsClose all backend connections gracefully.

        Safe to call even if ``initialize()`` was never called.
        NFzMemoryGateway closed)
rI   acloserj   rG   closerH   rL   rJ   rM   rN   rT   s    r   r{   zMemoryGateway.close   s)    
 ;;"kk((*** DK==$mm))+++ !DM<<#ll((***  DL<<#ll((*** "*+5 +  ,  +  + s   D5C3 C1C3 D5D #D$D (D5<D DD D52D& D$D& D51C3 3	C?<D5>C??D5D 	DD5DD5D 	D!D5 D!!D5$D& &	D2/D51D22D5c                8  K   | j                          |xs i }| j                  j                  |       d{   }| j                  ||j                         d{   }|s9t
        |j                     }| j                  |       d{   }	t        |||	      | j                  j                  |||      \  }
}|t        j                  k(  r7t        j                  d||
       t        |||||
t        j                  |      S t        ||      }| j!                  ||       d{   }|t        j                  d||dd        |S | j#                  |       d{   }t%        t'                     }t        |||||
|i |d|i|      }|t        j(                  k(  r| j+                  |||       d{    n|t        j,                  k(  rA| j/                  |||||       d{   }||_        | j3                  ||||       d{    nn|t        j4                  k(  r[| j/                  |||||       d{   }||_        | j3                  ||||       d{    | j7                  |||       d{    | j9                  |       d{    	 t:        j=                  |j>                        jA                          t        jE                  d	||j>                  |
|       |S 7 7 7 i7 7 7 D7 7 7 7 7 7 u# tB        $ r Y Ow xY ww)
a  Write a memory through the full pipeline.

        Pipeline:
          1. Check tenant exists (via EntitlementLedger)
          2. Check daily quota
          3. Score content (SurpriseIntegration)
          4. Route by tier (DISCARD / WORKING / EPISODIC / SEMANTIC)
          5. Dedup by content hash
          6. Write to appropriate backends
          7. Increment quota counter
          8. Return MemoryRecord

        Args:
            tenant_id: Tenant UUID.
            content: Memory content string.
            source: Origin of the content.
            domain: Domain/topic category.
            metadata: Optional dict of additional metadata.

        Returns:
            MemoryRecord describing the stored memory.

        Raises:
            QuotaExceededError: When tenant is at their daily limit.
            GatewayNotInitializedError: When called before initialize().
        Nz+Memory discarded for tenant %s (score=%.3f))r   contentsourcedomainsurprise_scorememory_tiermetadatau4   Duplicate memory detected for tenant %s (hash=%s…)   content_hashr   r}   r~   r   r   r   r   	vector_id)tierz2Memory written: tenant=%s tier=%s score=%.3f id=%s)#_require_initializedrL   get_manifest_check_quotar   r4   _get_quota_countr   rK   score_contentr   DISCARDrM   debugr   _compute_hash_find_by_hash_embedr/   r   WORKING_write_workingEPISODIC	_write_pgpg_id_write_qdrantSEMANTIC_write_hot_cache_increment_quota_GATEWAY_WRITESlabelsvalueincrj   rN   )r   r   r}   r~   r   r   manifest	has_quotadaily_limitr   scorer   r   existingvectorr-   recordr   s                     r   write_memoryzMemoryGateway.write_memory"  s    D 	!!#>r 229==++Ix}}EE	%hmm4K 11)<<G$YWEE nn227FFKt :%%%LL=y%  #$&..!  %Y8++I|DDLLF<, O {{7++ L	 ??.,?	
 :%%%%%iFCCCZ(((..Ivv|\\E FL$$Y	66JJJZ(((..Ivv|\\E FL$$Y	66JJJ''	9fEEE ##I...	""

"3779 	@tzz5)	
 _ >E =0 E ,  D ]J ]JE 	/
  		s   5LK,#LK/-L	K2
BL&K5'6LK8AL7K;80L(K>)"LL0L<L="LL L:L;LL	L.L &L/L2L5L8L;L>LLLLL	L	LLLLc                  K   | j                          |r|j                         sg S g }| j                  |	 | j                  j                         4 d{   }|j	                  dt        |      |       d{   }|D ]  }|j                  t        ||              ddd      d{    |S |S 7 ^7 <7 # 1 d{  7  sw Y   |S xY w# t        $ r"}t        j                  d||       Y d}~|S d}~ww xY ww)aT  Retrieve memories for a tenant, ordered by recency.

        Args:
            tenant_id: Tenant UUID.
            query: Query string (used for filtering/ordering; not vector search).
            limit: Max results.

        Returns:
            List of MemoryRecord, newest first. Empty if no memories or
            empty query.
        Na  
                        SELECT id, memory_id, content, source, domain,
                               surprise_score, memory_tier, metadata,
                               created_at, vector_id
                        FROM rlm_memories
                        WHERE tenant_id = $1
                        ORDER BY created_at DESC
                        LIMIT $2
                        z"PG read_memories failed for %s: %s)r   striprG   acquirefetchr/   append_row_to_recordrj   rM   rk   )	r   r   queryr   recordsconnrowsrowrv   s	            r   read_memorieszMemoryGateway.read_memories  s    " 	!!#EKKMI&(==$S==002 G Gd!% I" D  $ G~c9'EFGG G& w'GG G G G&   SA9cRRSs   3C=C B3C  B98B59&B9C *B7+C /C=3C 5B97C 9C?C CC 
C=C 	C:C5/C=5C::C=c                  K   | j                          |r|j                         sg S | j                  |       d{   }g }	 ddlm}m}m}	 | j                  j                  t        ||| | |d |	t        |                  g             d{   }
|
D ]  }|j                  xs i }t        ||j                  d	d
      |j                  dd
      |j                  dd
      |j                  dd      t        |j                  dd            |j                  di       |j                        }|j!                  |        	 |S 7 7 # t"        $ r"}t$        j'                  d||       Y d}~|S d}~ww xY ww)u  Vector-search memories for a tenant using Qdrant.

        Always tenant-isolated — never returns memories from other tenants.

        Args:
            tenant_id: Tenant UUID.
            query: Natural-language query for semantic search.
            limit: Max results.
            min_score: Minimum similarity score threshold.

        Returns:
            List of MemoryRecord ordered by similarity score.
            Empty list if query is empty or no results above min_score.
        Nr   )FilterFieldCondition
MatchValuer   )r   )keymatch)must)collection_namequery_vectorr   score_thresholdquery_filterr}   r:   r~   r   r           r   episodicr   r   zQdrant search failed for %s: %s)r   r   r   qdrant_client.modelsr   r   r   rH   searchrn   r/   payloadr   rB   r   idr   rj   rM   rk   )r   r   r   r   	min_scorer   r   r   r   r   resultshitr   r   rv   s                  r   search_memorieszMemoryGateway.search_memories  sn    * 	!!#EKKMI![[//&(	LOO LL// 2) )#& +",3y>"B 0  G  '+++%'#KK	26";;x4";;x4#*;;/?#E *7;;}j+Q R$[[R8!ff	 v&'  G 0
6  	LLL:IsKK	LsM   9E=E
E=AE EB2E E=E 	E:E5/E=5E::E=c                  K   | j                          | j                  ||       d{   }|st        j                  d||       yg }| j	                  ||       d{   }|s|j                  d       | j                  |       d{   }|s|j                  d       | j                  ||       d{   }|s|j                  d       |r|r|st        ||      t        j                  d|||       y7 7 7 s7 Hw)	a  Delete a memory from all backends.

        Cross-tenant deletes silently return False (no deletion, no error).
        Raises PartialDeleteError if some backends succeed and others fail.

        Args:
            tenant_id: Tenant UUID (used for ownership verification).
            memory_id: The memory_id / vector_id of the record.

        Returns:
            True if the memory existed and was deleted.
            False if the memory was not found.
        Nz1delete_memory: no record found (tenant=%s, id=%s)F
postgresqlqdrantredisz-Memory deleted: tenant=%s id=%s (failures=%s)T)
r   _pg_ownsrM   r   
_pg_deleter   _qdrant_delete_redis_delete_memoryr+   rN   )r   r   r-   existsfailurespg_ok	qdrant_okredis_oks           r   delete_memoryzMemoryGateway.delete_memory  s     $ 	!!# }}Y	::LLC9   ooi;;OOL) --i88	OOH% 229iHHOOG$U)$Y99;y(	
 C ; <
 9
 IsF   &DC:5DC<+D
C>,D7D 8AD<D>D Dc                  K   | j                   y	 | j                   j                         4 d{   }|j                  dt        |      |       d{   }|	 ddd      d{    yt	        ||      cddd      d{    S 7 Y7 77 &7 # 1 d{  7  sw Y   yxY w# t
        $ r }t        j                  d|       Y d}~yd}~ww xY ww)z9Look up an existing record by content hash in PostgreSQL.NaL  
                    SELECT id, memory_id, content, source, domain,
                           surprise_score, memory_tier, metadata,
                           created_at, vector_id
                    FROM rlm_memories
                    WHERE tenant_id = $1 AND content_hash = $2
                    LIMIT 1
                    zHash dedup lookup failed: %s)rG   r   fetchrowr/   r   rj   rM   warning)r   r   r   r   r   rv   s         r   r   zMemoryGateway._find_by_hashL  s      == 	}},,. 6 6$ MM 	N   ;6 6 6 &c956 6 66 6 6 6 6   	NN93?	s   CB& B	B&  BBBB& &B'B& +C,B7B& BB& C	B& BB& B& B#BB#B& "C#B& &	C/C
C
CCc                *  K   | j                   sddddddS i }	 | j                  j                         4 d{   }|j                  d       d{    ddd      d{    d|d<   	 | j                  j                          d{    d|d
<   	 | j                  j                          d{    d|d<   t        |j                               }d|rdi|S di|S 7 7 7 # 1 d{  7  sw Y   xY w# t        $ r%}t
        j                  d	|       d|d<   Y d}~d}~ww xY w7 # t        $ r%}t
        j                  d|       d|d
<   Y d}~d}~ww xY w7 # t        $ r%}t
        j                  d|       d|d<   Y d}~d}~ww xY ww)u   Ping all three backends and return health status.

        Returns:
            Dict with keys: status ("healthy" | "degraded"),
            pg, qdrant, redis — each True/False.
        degradedFznot initialized)statuspgr   r   reasonNzSELECT 1Tr   zPG health check failed: %sr   zQdrant health check failed: %sr   zRedis health check failed: %sr   healthy)rJ   rG   r   fetchvalrj   rM   r   rH   get_collectionsrI   rr   allvalues)r   r   r   rv   all_healthys        r   health_checkzMemoryGateway.health_checkk  s       $+  $&	"}},,. 0 0$mmJ///0 0 GDM	&,,..000 $GH	%++""$$$#GG
 '..*+;i

 	
4>

 	
10/0 0 0 0  	"NN7=!GDM	" 1 	&NN;SA %GH	& % 	%NN:C@$GG	%s   FC< C!C< C'C#C'C< !C%"	C< ,D/ 	D-
	D/ E" 1E 2	E" ;&F!C< #C'%C< 'C9-C0.C95C< <	D*D% F%D**F-D/ /	E8EFEF E" "	F+FFFFc                h   K   t         |   }|dk(  ry| j                  |       d{   }||k  S 7 	w)z>Return True if tenant has remaining quota, False if exhausted.r3   TN)r4   r   )r   r   r   r   r   s        r   r   zMemoryGateway._check_quota  s<     D!B;--i88 9s   $20
2c                $  K   t        j                  t        j                        j	                  d      }t
        j                  ||      }	 | j                  j                  |       d{   }|t        |      S dS 7 # t        $ r Y yw xY ww)z8Return today's write count from Redis (falls back to 0).%Y-%m-%dr   dateNr   )r   nowr   utcstrftime
_QUOTA_KEYformatrI   rB   r    rj   )r   r   todayr   vals        r   r   zMemoryGateway._get_quota_count  s{     X\\*33J?)%@	,,C"3s85A5 - 		sH   A
BB +A?,B <B=B >B?B 	B
BBBc                  K   t        j                  t        j                        j	                  d      }t
        j                  ||      }	 | j                  j                  |       d{   }|dk(  r)| j                  j                  |t               d{    yy7 37 # t        $ r!}t        j                  d||       Y d}~yd}~ww xY ww)z<Increment the daily quota counter in Redis with 25-hour TTL.r   r   Nr   z!Quota increment failed for %s: %s)r   r   r   r   r   r   r   rI   increxpire
_QUOTA_TTLrj   rM   r   )r   r   r   r   countrv   s         r   r   zMemoryGateway._increment_quota  s     X\\*33J?)%@	P++**3//Ezkk((j999  09 	PNN>	3OO	PsT   A
CB# +B,,B# B!B# CB# !B# #	C,CCCCc                $  K   |r|j                         s
dgt        z  S t        dz  }t        |      |kD  r|d| }t        j                  d|       t        j                  j                  d      xs t        j                  j                  d      }|r~	 ddl	m
} |j                  |       |j                  d	|d
      }|d   }t        |      t        k(  r|S t        |      t        k  r|dgt        t        |      z
  z  z   }|S |dt         }|S t        j                  j                  dd      }	 ddl}	|	j!                  d      4 d{   }
|
j#                  | dd|d       d{   }|j%                          |j'                         }|j                  dg       }|rZt        |      t        k7  r5t        |      t        k  r|dgt        t        |      z
  z  z   }n	|dt         }|cddd      d{    S ddd      d{    dgt        z  S # t        $ r!}t        j                  d|       Y d}~.d}~ww xY w7 7 7 P7 A# 1 d{  7  sw Y   QxY w# t        $ r }t        j                  d|       Y d}~xd}~ww xY ww)a5  Generate a 768-dim embedding for content.

        Priority:
          1. Google GenAI text-embedding-004 (768-dim native)
          2. Ollama nomic-embed-text fallback

        Args:
            content: Text to embed.

        Returns:
            List of 768 floats. Zero vector for empty content.
        r      Nz+Content truncated to %d chars for embeddingGOOGLE_API_KEYGEMINI_API_KEYr   )r_   zmodels/text-embedding-004RETRIEVAL_DOCUMENT)modelr}   	task_type	embeddingz/Google embed failed, falling back to Ollama: %s
OLLAMA_URLzhttp://localhost:11434rX   )r`   z/api/embeddingsznomic-embed-text)r   prompt)jsonz*Ollama embed failed, using zero vector: %s)r   r   _MAX_EMBED_TOKENSlenrM   r   r@   rA   rB   google.generativeaigenerativeai	configureembed_contentrj   r   httpxAsyncClientpostraise_for_statusr   )r   r}   	max_chars
google_keygenairesultr   rv   
ollama_urlr  clientrespdatas                r   r   zMemoryGateway._embed  s     gmmo5=(( &)	w<)#jy)GLLF	R ZZ^^$45YHX9Y
W3
3,,5#2 - 
  ,v;-/!Mv;.#se}s6{/J&KKF  $N]3F
 ZZ^^L2JK
	N(((4 " "#[[!l/2#5I )   %%'yy{+r26{m3v;6%+se}s6{7R.S%SF%+N]%;F!" " " " "$ u}$$3  WPRUVVW"" " " " "  	NNNGMM	Ns   BJAH J,H J
H !J2I$ II$ I-I	.B I.I$ :I;I$ ?J I$ II$ 
J	I#H?9J?IJI$ 	II$ I$ I!II!I$ $	J-JJJJc                  K   t         j                  ||      }ddl}|j                  |t	        |      |j
                  |j                  |j                  |j                  |j                  j                  |j                  |j                  j                         d	      }	 | j                  j                  ||d       d{    y7 # t         $ r }t"        j%                  d|       Y d}~yd}~ww xY ww)z2Store a WORKING-tier memory in Redis with 24h TTL.r   r-   r   N	r-   r   r}   r~   r   r   r   r   
created_atiQ exzRedis working write failed: %s)_WORKING_KEYr   r   dumpsr/   r}   r~   r   r   r   r   r   r  	isoformatrI   setrj   rM   r   r   r-   r   r   r   _jsonr   rv   s           r   r   zMemoryGateway._write_working  s      !!I!K++"Y~~mmmm$33!--33 ++557

 
	B++//#w5/999 	BNN;SAA	BB   BC6!!C
 CC
 C6C
 
	C3C.)C6.C33C6c                  K   t         j                  ||      }ddl}|j                  |t	        |      |j
                  |j                  |j                  |j                  |j                  j                  |j                  |j                  j                         d	      }	 | j                  j                  ||d       d{    y7 # t         $ r }t"        j%                  d|       Y d}~yd}~ww xY ww)z9Store a SEMANTIC-tier memory in Redis hot cache (1h TTL).r  r   Nr  i  r  z Redis hot-cache write failed: %s)_HOT_CACHE_KEYr   r   r  r/   r}   r~   r   r   r   r   r   r  r  rI   r  rj   rM   r   r  s           r   r   zMemoryGateway._write_hot_cache$  s      ##i9#M++"Y~~mmmm$33!--33 ++557

 
	D++//#w4/888 	DNN=sCC	Dr  c                  K   ddl }| j                  j                         4 d{   }|j                  d|t	        |      |j
                  |j                  |j                  |j                  |j                  j                  |||j                  |j                        |j                         d{   }ddd      d{    |S 7 7 7 
# 1 d{  7  sw Y   S xY ww)zWInsert a memory row into PostgreSQL.

        Returns the generated PG row id.
        r   NaW  
                INSERT INTO rlm_memories
                    (memory_id, tenant_id, content, source, domain,
                     surprise_score, memory_tier, content_hash,
                     embedding, metadata, created_at)
                VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::float8[],$10,$11)
                RETURNING id
                )r   rG   r   r   r/   r}   r~   r   r   r   r   r  r   r  )	r   r-   r   r   r   r   r  r   row_ids	            r   r   zMemoryGateway._write_pg=  s      	==((* 	 	d== I%%""((FOO,!!' F	 	, -		 	 	 	, sW   #C$CC$BC1C
2C6C$CC$
CC$C!CC!C$c                f  K   ddl m}  |||t        |      |j                  |j                  |j
                  |j                  |j                  j                  |j                  |j                  j                         d      }| j                  j                  t        |g       d{    y7 w)z6Upsert a point into Qdrant with tenant-tagged payload.r   )PointStruct)r   r}   r~   r   r   r   r   r  )r   r   r   )r   pointsN)r   r%  r/   r}   r~   r   r   r   r   r   r  r  rH   upsertrn   )r   r-   r   r   r   r%  points          r   r   zMemoryGateway._write_qdrantb  s      	5 ^!>> -- --"("7"7%1177"OO$//99;	
 ll!!.7 " 
 	
 	
s   B'B1)B/*B1c                l  K   | j                   y	 | j                   j                         4 d{   }|j                  d|t        |             d{   }|ducddd      d{    S 7 =7 7 	# 1 d{  7  sw Y   yxY w# t        $ r }t
        j                  d|       Y d}~yd}~ww xY ww)z0Return True if tenant owns this memory_id in PG.NFzCSELECT id FROM rlm_memories WHERE memory_id = $1 AND tenant_id = $2zPG owns check failed: %s)rG   r   r   r/   rj   rM   r   )r   r   r-   r   r   rv   s         r   r   zMemoryGateway._pg_owns  s     == 
	}},,. ' '$ MMY	N 
 $' ' '' ' ' '  	NN5s;	s   B4B A-B  A3A/A3B 'A1(B ,B4-B /A31B 3B9A<:BB B4B 	B1B,'B4,B11B4c                V  K   	 | j                   j                         4 d{   }|j                  d|t        |             d{   }|dk(  cddd      d{    S 7 >7 7 	# 1 d{  7  sw Y   yxY w# t        $ r!}t
        j                  d||       Y d}~yd}~ww xY ww)z6Delete a row from PostgreSQL. Returns True on success.Nz@DELETE FROM rlm_memories WHERE memory_id = $1 AND tenant_id = $2zDELETE 1zPG delete failed for %s: %sF)rG   r   executer/   rj   rM   rk   )r   r   r-   r   r  rv   s         r   r   zMemoryGateway._pg_delete  s     
	}},,. , ,$#||V	N  
 +, , ,, , , ,  	LL6	3G	s   B)A< A!A<  A'A#A'A< A%A<  B)!A< #A'%A< 'A9-A0.A95A< 8B)9A< <	B&B!B)!B&&B)c                   K   	 ddl m} | j                  j                  t         ||g             d{    y7 # t
        $ r!}t        j                  d||       Y d}~yd}~ww xY ww)	z/Delete a Qdrant point. Returns True on success.r   )PointIdsList)r&  )r   points_selectorNTzQdrant delete failed for %s: %sF)r   r-  rH   deletern   rj   rM   rk   )r   r-   r-  rv   s       r   r   zMemoryGateway._qdrant_delete  sk     		9,,%% 2 ,YK @ &    	
  	LL:IsK	s7   A+2> <> A+> 	A(A#A+#A((A+c                  K   	 t         j                  ||      }t        j                  ||      }| j                  j	                  ||       d{    y7 # t
        $ r!}t        j                  d||       Y d}~yd}~ww xY ww)z3Delete working + hot-cache Redis keys for a memory.r  NTzRedis delete failed for %s: %sF)r  r   r!  rI   r/  rj   rM   r   )r   r   r-   working_keyhot_keyrv   s         r   r   z"MemoryGateway._redis_delete_memory  s     	&--#y . K %++#y , G ++$$['::: ; 	NN;YL	sA   BAA AA BA 	B"A>9B>BBc                  K   ddl m}m} 	 | j                  j	                          d{   }|j
                  D cg c]  }|j                   }}t        |vr_| j                  j                  t         |t        |j                               d{    t        j                  dt        t               yy7 c c}w 7 ,# t        $ r }t        j                  d|       Y d}~yd}~ww xY ww)z2Create the Qdrant collection if it does not exist.r   )DistanceVectorParamsN)sizedistance)r   vectors_configz'Created Qdrant collection '%s' (dim=%d)z&Could not ensure Qdrant collection: %s)r   r4  r5  rH   r   collectionsnamern   create_collectionr   COSINErM   rN   rj   r   )r   r4  r5  r9  cnamesrv   s          r   rm   z'MemoryGateway._ensure_qdrant_collection  s     ?	J $ < < >>K%0%<%<=QVV=E=!.ll44$6#/*!)$ 5    =& / ?=  	JNNCSII	Js\   	C.C B9C B;AC C #C 7C.9C ;C 	C+C&!C.&C++C.c                2    | j                   st        d      y)z8Raise GatewayNotInitializedError if not yet initialized.z8Call await gateway.initialize() before using the gatewayN)rJ   r1   rT   s    r   r   z"MemoryGateway._require_initialized  s       ,J  !r   )NNNN)
rO   Optional[str]rP   r@  rQ   r@  rR   r@  r!   r"   )r!   bool)r!   r"   N)r   r   r}   r/   r~   r/   r   r/   r   zOptional[Dict[str, Any]]r!   r   )rX   )r   r   r   r/   r   r    r!   List[MemoryRecord])rX   r   )
r   r   r   r/   r   r    r   floatr!   rC  )r   r   r-   r/   r!   rA  )r   r   r   r/   r!   zOptional[MemoryRecord])r!   zDict[str, Any])r   r   r   r   r!   rA  )r   r   r!   r    )r   r   r!   r"   )r}   r/   r!   List[float])r-   r/   r   r   r   r   r!   r"   )r-   r/   r   r   r   r   r   rE  r   r/   r!   r    )
r-   r/   r   r   r   r   r   rE  r!   r"   )r-   r/   r!   rA  )r$   r%   r&   r'   r   propertyrU   rx   r{   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rm   r   r2   r   r   r7   r7   p   sy   	" !%$((,#''
'
 "'
 &	'

 !'
 
'
R ! !KHZ!,Z .2vv v 	v
 v +v 
v@ 	-- - 	-
 
-f == = 	=
 = 
=F66 6 
	6x-0	>/
j	PC%RBB B 	B
 
B2DD D 	D
 
D2## # 	#
 # # 
#J

 
 	

 
 

@ &J2r   r7   c                 z    	 ddl m} m}  | dddg      } | dddg      } |d	d
      }|||fS # t        $ r Y yw xY w)zInitialize Prometheus metrics counters and histograms.

    Returns a namespace dict with the metric objects, or None if
    prometheus_client is not installed.
    r   )Counter	Histogrammemory_gateway_writes_totalzTotal memory writes by tierr   memory_gateway_errors_totalz!Total gateway errors by operation	operationmemory_gateway_write_secondszMemory write latency in seconds)NNN)prometheus_clientrH  rI  ImportError)rH  rI  writes_totalerrors_totalwrite_secondss        r   _setup_prometheusrS    si     8))H

 )/M

 "*-
 \=88   s   +. 	::c                h    t        j                  dd| j                         j                               S )z4Lowercase and collapse whitespace for dedup hashing.z\s+ )resublowerr   )r}   s    r   _normalise_contentrY    s$    66&#w}}44677r   c                    t        |      }|  d| j                  d      }t        j                  |      j	                         S )zCompute SHA-256 hash of (tenant_id + normalised content).

    Including tenant_id ensures the same text from two tenants gets
    different hashes (correct isolation).
    :zutf-8)rY  encodehashlibsha256	hexdigest)r   r}   
normalisedraws       r   r   r     sA     $G,JKq
%
,
,W
5C>>#((**r   c                   ddl }ddd} || dd      }t        |t              r	 |j                  |      }nt        |t
              r|}ni } || d      }t        |t              r	 t        j                  |      }n,t        |t              r|}nt        j                  t              } || dd      }	 t        |      }	t        | || d	d
       || dd
       || dd
      t         || dd            |	|| || d      xs	  || d       || d      
      S # t        $ r i }Y w xY w# t        $ r t        j                  t              }Y w xY w# t        $ r t        j                  }	Y w xY w)z3Convert an asyncpg Row (or dict) to a MemoryRecord.r   Nc                F    	 | |   S # t         t        t        f$ r |cY S w xY wrB  )KeyError	TypeError
IndexError)rr   defaults      r   _getz_row_to_record.<locals>._get  s+    	S6M)Z0 	N	s      r   z{}r  r   workingr}   r:   r~   r   r   r   r   r-   r   )
r   r}   r~   r   r   r   r   r  r   r   rB  )rg  r   r   r/   rh  r   r!   r   )r   
isinstancer/   loadsrj   dictr   fromisoformatr   r   r   rg   r   r   rD  )
r   r   r  ri  meta_rawmetacreated_rawr  tier_valr   s
             r   r   r     ss    CT*H(C 	;;x(D 
Hd	#sL)K+s#	+!//<J 
K	* 
\\#&
C	2H"(# S)R(C2&C2&T#'7=>sK(BDk,B3o 1  	D	  	+!c*J	+  "!!"s5   D &D& 3E D#"D#&"E
EE*)E*)r!   r   )r}   r/   r!   r/   )r   r   r}   r/   r!   r/   )r   r   r   r   r!   r   )8r'   
__future__r   r]  loggingr@   rV  timer   r   r   typingr   r   r	   r
   r   uuidr   r   	contractsr   r   r   r   r   	getLoggerrM   rj   r   r+   r1   STARTERPROFESSIONAL
ENTERPRISEQUEENr4   __annotations__r   r  r!  r   _DISCARD_THRESHOLD_EPISODIC_THRESHOLD_SEMANTIC_THRESHOLDr  	_PG_TABLErn   r7   rS  r   _GATEWAY_ERRORS_GATEWAY_WRITE_SECONDSrY  r   r   r2   r   r   <module>r     s$  ( #   	 	  , , 3 3   
		-	.

 

	
 	
F F #tV	($  ,
42
       	 $ n nj 6 <M;N 8"88
+1r   