
    iaI                        U d Z ddl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 ddl	m
Z
 ddlmZ ddlZej                  j                  dd       ddlmZmZ ej&                  j)                  dd	      Zeed
<    eej&                  j)                  dd            Zeed<   ej&                  j)                  dej&                  j)                  d            Zee   ed<   ej&                  j)                  dd      j7                         dv Zeed<   dZeed<    e
d      Ze
ed<    e h d      Z!e ed<    e"dd      Z#e"ed<    G d de$      Z% G d  d!      Z&y)"u;  
KingRegistry — Kinan directive store.
Manages the King's (Kinan's) directives with priority ordering.

AIVA RLM Nexus — Story 2.03 + 2.05 — Track A
File: core/registry/king_registry.py

Schema (002_king_queen_tables.sql):
    kinan_directives (
        directive_id    UUID PK,
        directive_text  TEXT NOT NULL,
        priority        INTEGER CHECK (1-5),
        source          VARCHAR CHECK (voice/text/inferred),
        status          VARCHAR CHECK (active/fulfilled/cancelled),
        captured_at     TIMESTAMP,
        related_entities JSONB DEFAULT '[]'
    )

RULE COMPLIANCE:
  - Rule 7:  No SQLite — uses Elestio PostgreSQL via ConnectionFactory
  - Rule 6:  E: drive only — lives at /mnt/e/genesis-system/core/registry/
  - Rule 14: RegistryError wraps all DB exceptions for clean caller surface
    N)datetimetimezone)Path)Optionalz/mnt/e/genesis-system)ConnectionFactoryconnectionsQDRANT_HOSTz"qdrant-b3knu-u50607.vm.elestio.app_QDRANT_HOSTQDRANT_PORT6333_QDRANT_PORTQDRANT_API_KEYGENESIS_QDRANT_API_KEY_QDRANT_API_KEYQDRANT_HTTPStrue)1r   yes_QDRANT_HTTPSkinan_directives_QDRANT_COLLECTIONz(/mnt/e/genesis-system/data/observability_EVENTS_DIR>   textvoiceinferredVALID_SOURCES      VALID_PRIORITIESc                       e Zd ZdZy)RegistryErrorz@Raised on unrecoverable KingRegistry failures (DB errors, etc.).N)__name__
__module____qualname____doc__     4/mnt/e/genesis-system/core/registry/king_registry.pyr!   r!   C   s    Jr'   r!   c            
           e Zd ZdZddee   ddfdZdedededefd	Z	dd
ede
fdZdede
fdZdededededef
dZddedede
fdZdededdfdZdede
fdZdede
defdZdedefdZy)KingRegistrya7  
    Kinan directive store.

    Provides add / read / fulfil lifecycle for King directives backed by
    Elestio PostgreSQL (kinan_directives table).

    Args:
        connection_factory: Optional ConnectionFactory override for testing.
                            Defaults to the module-level singleton.
    Nconnection_factoryreturnc                 .    ||| _        y t         | _        y N)r   _cf)selfr+   s     r(   __init__zKingRegistry.__init__W   s    );)G%[r'   r   prioritysourcec                    |t         vrt        d|      |t        vrt        dt        t               d|      t	        t        j                               }t        j                  t        j                        }	 | j                  j                         }|j                         }|j                  d|||||f       |j                          |j!                          	 | j)                  |||d       |S # t"        $ rM}	 | j                  j                         j%                          n# t"        $ r Y nw xY wt'        d|       |d}~ww xY w# t"        $ r Y |S w xY w)u  
        Insert a new Kinan directive and return its UUID.

        Args:
            text:     Directive content (stored in directive_text column).
            priority: Integer 1-5 (5 = highest urgency).
            source:   Origin channel — "voice", "text", or "inferred".

        Returns:
            directive_id as a UUID string.

        Raises:
            ValueError:      priority not in 1-5, or source not in VALID_SOURCES.
            RegistryError:   Any database-level failure.
        u   priority must be 1–5, got zsource must be one of z, got z
                INSERT INTO kinan_directives
                    (directive_id, directive_text, priority, source, status, captured_at)
                VALUES
                    (%s, %s, %s, %s, 'active', %s)
                zadd_directive failed: Nactive)r   
ValueErrorr   sortedstruuiduuid4r   nowr   utcr/   get_postgrescursorexecutecommitclose	Exceptionrollbackr!   _write_to_qdrant)	r0   r   r2   r3   directive_idcaptured_atconncurexcs	            r(   add_directivezKingRegistry.add_directive^   s`   , ++.xl;  &()>(?vfZP  4::<(ll8<<0	I88((*D++-CKK tXv{C KKMIIK	!!,hI   	I%%'002 "8 >?SH	I  		sI   >A!C6  E 6	E (D)(E)	D52E4D55EE	EEtop_nc           
         	 | j                   j                         }|j                         }|j                  d|f       |j	                         }|j                          g }|D ]T  }|d   }|j                  t        |d         |d   |d   t        |d      r|j                         n
t        |      d	       V |S # t        $ r}t        d|       |d}~ww xY w)
a$  
        Return the top-N active directives ordered by priority DESC, then
        captured_at ASC (oldest high-priority first).

        Args:
            top_n: Maximum number of records to return (default 5).

        Returns:
            List of dicts:
                {
                    "directive_id": str,
                    "text":         str,
                    "priority":     int,
                    "captured_at":  str  (ISO-8601)
                }

        Raises:
            RegistryError: Any database-level failure.
        a   
                SELECT directive_id, directive_text, priority, captured_at
                FROM   kinan_directives
                WHERE  status = 'active'
                ORDER  BY priority DESC, captured_at ASC
                LIMIT  %s
                zget_active_directives failed: N   r   r      	isoformat)rE   r   r2   rF   )r/   r=   r>   r?   fetchallrA   rB   r!   appendr8   hasattrrO   )	r0   rK   rG   rH   rowsrI   resultrowcaptureds	            r(   get_active_directivesz"KingRegistry.get_active_directives   s    (	Q88((*D++-CKK 	 <<>DIIK  	C1vHMM$'AK$'F$'F #8[9 !**, ]		 %  	Q"@ FGSP	Qs   AB< <	CCCc           
         t        j                  |j                               j                         }d}t	        j
                  |t        |      z        }||z  d| }t        d|d      D cg c]!  }t        j                  d|||dz          d   # }}t        d |D              xs d}t	        j                  |      r|dk(  rd}|dd	 D 	cg c]  }	|	|z  	 c}	S c c}w c c}	w )
u  
        Deterministic 768-dim embedding derived from the SHA-256 hash of text.

        This is a placeholder for a real embedding model.  It is deterministic
        (same text → same vector) and normalised to [-1, 1].  The real model
        will be swapped in later without changing the caller interface.

        Args:
            text: Directive text to embed.

        Returns:
            List of 768 floats in [-1, 1].
        i   Nr      fc              3   2   K   | ]  }t        |        y wr.   )abs).0vs     r(   	<genexpr>z2KingRegistry._text_to_embedding.<locals>.<genexpr>   s     -c!f-s   g      ?g           )hashlibsha256encodedigestmathceillenrangestructunpackmaxisfinite)
r0   r   hneeded_bytesrepeat_countextendedivaluesmax_valr^   s
             r(   _text_to_embeddingzKingRegistry._text_to_embedding   s     NN4;;=)002yyA!67$m|4FKA|]^F_`&--Xa!a%%89!<``-f--4}}W%CG%+DS\2G22 a
 3s   ,&CC!rE   statusc           	          	 ddl m} ddlm}m}m}  |t        t        t        t              }	|	j                         j                  D 
cg c]  }
|
j                   }}
t        |vr)|	j                  t         |d|j                               | j!                  |      } |||||||d	      }|	j#                  t        |g
       yc c}
w # t$        $ r%}| j'                  |t)        |             Y d}~yd}~ww xY w)ux  
        Embed directive text and upsert to Qdrant ``kinan_directives`` collection.

        Qdrant is a secondary index — Postgres is the source of truth.
        Failure here is non-fatal; callers must catch and suppress exceptions
        OR use the bare ``try/except: pass`` pattern.

        Args:
            directive_id: UUID string — used as Qdrant point ID.
            text:         Directive text to embed and store.
            priority:     Integer 1-5, stored in payload.
            status:       Current status string, stored in payload.

        Returns:
            True on success, False on failure.
        r   QdrantClient)DistancePointStructVectorParamshostportapi_keyhttpsr`   )sizedistance)collection_namevectors_config)rE   r   r2   ru   )idvectorpayload)r   pointsTNF)qdrant_clientrx   qdrant_client.modelsry   rz   r{   r
   r   r   r   get_collectionscollectionsnamer   create_collectionCOSINErt   upsertrB   _log_qdrant_warningr8   )r0   rE   r   r2   ru   rx   ry   rz   r{   clientcexistingr   pointrI   s                  r(   rD   zKingRegistry._write_to_qdrant   s    .#	2PP!!!'#	F )/(>(>(@(L(LM1MHM!1(($6#/S8??#S ) 
 ,,T2F$0  ($		E MM*<eWMM' N*  	$$\3s8<	s+   AC 
C
A,C 
C 	C=C88C=querytop_kc           	         	 ddl m}  |t        t        t        t
              }| j                  |      }|j                  t        ||d      }g }|j                  D ]d  }|j                  xs i }	|j                  |	j                  dt        |j                              |	j                  dd      |j                  d	       f |S # t         $ r'}
| j#                  d
t        |
             g cY d}
~
S d}
~
ww xY w)a  
        Semantic search over directives in Qdrant.

        Embeds ``query`` and returns the top-k nearest points.
        Returns an empty list on Qdrant failure (non-fatal).

        Args:
            query: Free-text query string.
            top_k: Maximum number of results to return (default 3).

        Returns:
            List of dicts: [{"directive_id": str, "text": str, "score": float}]
            Returns [] on Qdrant failure or when no matches are found.
        r   rw   r|   T)r   r   limitwith_payloadrE   r    )rE   r   scoresearchN)r   rx   r
   r   r   r   rt   query_pointsr   r   r   rQ   getr8   r   r   rB   r   )r0   r   r   rx   r   r   responseresultsr   r   rI   s              r(   search_directiveszKingRegistry.search_directives3  s     	2!!!'#	F ,,U3F** 2!	 + H G! ---2(/NCM(R 'FB 7!& N 	$$Xs3x8I	s   CC 	C5C0*C50C5contexterrorc                 \   	 t         j                  dd       t        j                  t        j
                        j                         d||d}t        t         dz  d      5 }|j                  t        j                  |      dz          ddd       y# 1 sw Y   yxY w# t        $ r Y yw xY w)	z;Append a warning event to observability log.  Never raises.T)parentsexist_okking_registry_qdrant_warning)	timestamp
event_typer   r   zevents.jsonla
N)r   mkdirr   r;   r   r<   rO   openwritejsondumpsrB   )r0   r   r   eventrZ   s        r(   r   z KingRegistry._log_qdrant_warningd  s    	dT:%\\(,,7AAC<"	E kN2C8 2A

5)D012 2 2 		s0   A B "(B
B BB B 	B+*B+enriched_memoryc                    d|vrt        d      |d   }|sg S | j                  d      }g }|D ]N  }| j                  ||      r| j                  |dd      }|j	                  |       |j	                  |dd       P |S )	aC  
        Extracts Kinan directives from a PostCallEnricher output.

        Looks at enriched_memory["kinan_directives"] list. Adds each item as
        a new directive with source='inferred' and priority=2. Deduplicates by
        skipping any directive whose text is semantically similar (substring
        match) to an existing active directive.

        Args:
            enriched_memory: dict with "kinan_directives" key containing a
                             list of directive strings produced by a
                             PostCallEnricher.

        Returns:
            List of newly added directive_ids (UUID strings). Empty list if all
            incoming directives were duplicates or the input list was empty.

        Raises:
            ValueError: if enriched_memory does not contain "kinan_directives".
        r   z5"kinan_directives" key is required in enriched_memoryd   )rK   rN   r   )r   r2   r3   )r   r2   )r6   rW   _is_duplicaterJ   rQ   )r0   r   incomingr   	added_idsdirective_textnew_ids          r(   infer_from_conversationz$KingRegistry.infer_from_conversationw  s    * _4G  ));< I --C-8	& 	EN!!.(;''#! ( F
 V$ OO^CD	E r'   new_textexisting_directivesc                 l    |j                         }|D ]  }|d   j                         }||v s||v s y y)a  
        Returns True if new_text is semantically similar to any existing directive.

        Uses simple case-insensitive substring matching in both directions:
          - new_text is a substring of an existing directive's text, OR
          - an existing directive's text is a substring of new_text.

        This is intentionally cheap (no vector similarity) so it runs fast
        inside the infer_from_conversation loop.

        Args:
            new_text:            Candidate directive text to check.
            existing_directives: List of directive dicts (must have "text" key).

        Returns:
            True if a duplicate is found, False otherwise.
        r   TF)lower)r0   r   r   	new_lower	directiveexisting_lowers         r(   r   zKingRegistry._is_duplicate  sI    $ NN$	, 	I&v.446NN*n	.I	 r'   c                    t        j                  t        j                        }	 | j                  j                         }|j                         }|j                  d|f       |j                  dkD  }|j                          |j                          |S # t        $ rM}	 | j                  j                         j                          n# t        $ r Y nw xY wt        d|       |d}~ww xY w)un  
        Transition a single active directive to 'fulfilled'.

        Args:
            directive_id: UUID string of the directive to fulfil.

        Returns:
            True  — directive found and updated.
            False — directive not found OR already fulfilled/cancelled.

        Raises:
            RegistryError: Any database-level failure.
        a  
                UPDATE kinan_directives
                SET    status       = 'fulfilled',
                       related_entities = COALESCE(related_entities, '[]'::jsonb)
                WHERE  directive_id = %s
                  AND  status       = 'active'
                r   zmark_fulfilled failed: N)r   r;   r   r<   r/   r=   r>   r?   rowcountr@   rA   rB   rC   r!   )r0   rE   fulfilled_atrG   rH   updatedrI   s          r(   mark_fulfilledzKingRegistry.mark_fulfilled  s      ||HLL1	J88((*D++-CKK 	 llQ&GKKMIIKN 	J%%'002 "9# ?@cI	Js6   A-B 	C)(CC$	CC$CC$$C)r.   )   )rM   )r"   r#   r$   r%   r   r   r1   r8   intrJ   listrW   rt   boolrD   r   r   dictr   r   r   r&   r'   r(   r*   r*   K   s&   	Y84E+F YRV Y>> > 	>
 
>H53 5t 5v3s 3t 34:: : 	:
 : 
:x/s /3 /t /b3 s t &1t 1 1fc   :'J3 'J4 'Jr'   r*   )'r%   ra   r   re   osri   r9   r   r   pathlibr   typingr   syspathinsertcore.db.connectionsr   r   environr   r
   r8   __annotations__r   r   r   r   r   r   r   r   	frozensetr   rh   r   rB   r!   r*   r&   r'   r(   <module>r      s1  .    	   '   
 * + > JJNN=2VWc W

}f=>c >!#JJNN+,"#  jjnn^V<BBDH\\t \, C , CDT D %%BCy C1+ % %KI KcJ cJr'   