
    Li{                    h    d Z ddlmZ ddlZddlZddlmZ ddlmZ ddlZddl	Zddl
Z G d d      Zy)u	  Session Store — L4 Cold Ledger session lifecycle manager.

Provides a typed, connection-pooled interface for creating, tracking, and
closing agent sessions in the Genesis sessions table.

    sessions — One row per agent session (start/end timestamps, agent ID,
               arbitrary metadata as JSONB).

Usage::

    from core.storage.session_store import SessionStore

    store = SessionStore(connection_params={
        "host": "...", "port": 5432, "user": "...",
        "password": "...", "dbname": "genesis"
    })

    session_id = store.open_session("forge-agent", {"task": "build_widget"})
    sessions   = store.get_active_sessions()
    session    = store.get_session(session_id)
    store.close_session(session_id)
    cleaned    = store.cleanup_orphaned_sessions()

    store.close()

Rules enforced (Genesis hardwired):
  - NO SQLite anywhere in this file
  - All SQL uses parameterised queries (%s placeholders — never f-strings)
  - Connection pool uses getconn/putconn in try/finally (no connection leaks)
    )annotationsN)datetime)Optionalc                  X    e Zd ZdZddZd ZddZdddZddZddZ	dd	Z
dd
ZddZy)SessionStoreu  Typed Postgres client for the Genesis sessions table (L4 Cold Ledger).

    Thread-safe via ThreadedConnectionPool. Every method acquires a connection
    from the pool and returns it in a ``try/finally`` block — connection leaks
    are structurally impossible.

    Args:
        connection_params: Dict accepted by ``psycopg2.connect`` — keys:
            ``host``, ``port``, ``user``, ``password``, ``dbname``.
            Optionally ``sslmode`` and other libpq parameters.
    c                Z    || _         t        j                  j                  di || _        y )N)   
   )_paramspsycopg2poolThreadedConnectionPool)selfconnection_paramss     3/mnt/e/genesis-system/core/storage/session_store.py__init__zSessionStore.__init__9   s&    (MM00L:KL 		    c                6    | j                   j                         S )zCReturn a connection from the pool (caller must putconn in finally).)r   getconnr   s    r   _acquirezSessionStore._acquireC   s    yy  ""r   c                :    | j                   j                  |       y)z3Return a connection to the pool without closing it.N)r   putconn)r   conns     r   _releasezSessionStore._releaseG   s    		$r   Nc                z   t        t        j                               }d}|t        j                  |      nd}| j                         }	 |j                         5 }|j                  ||||f       ddd       |j                          | j                  |       |S # 1 sw Y   ,xY w# | j                  |       w xY w)a  Create a new session row with started_at=NOW() and ended_at=NULL.

        Args:
            agent_id:  Identifier for the agent opening the session, e.g.
                       ``"forge-agent"`` or ``"parallel-builder"``.
            metadata:  Optional dict of arbitrary key/value data stored as
                       JSONB in the sessions table.

        Returns:
            The new session's UUID4 string.
        zZINSERT INTO sessions (id, started_at, agent_id, metadata) VALUES (%s::uuid, NOW(), %s, %s)N)
struuiduuid4jsondumpsr   cursorexecutecommitr   )r   agent_idmetadata
session_idsqlmetadata_jsonr   curs           r   open_sessionzSessionStore.open_sessionO   s     &
0 	 190D

8,$}}	  H#C*h!FGHKKMMM$H H MM$s$   
B' B0B' B$ B' 'B:c                   d}| j                         }	 |j                         5 }|j                  ||f       ddd       |j                          | j	                  |       y# 1 sw Y   +xY w# | j	                  |       w xY w)zMark a session as ended by setting ended_at = NOW().

        Args:
            session_id: UUID string of the session to close.
        z8UPDATE sessions SET ended_at = NOW() WHERE id = %s::uuidN)r   r"   r#   r$   r   )r   r'   r(   r   r*   s        r   close_sessionzSessionStore.close_sessionj   sr    # 	 }}	  0#C*/0KKMMM$	0 0 MM$s!   A. A"A. "A+'A. .Bc                r   d}| j                         }	 |j                  t        j                  j                        5 }|j                  |       |j                         }ddd       | j                  |       D cg c]  }t        |       c}S # 1 sw Y   3xY w# | j                  |       w xY wc c}w )zReturn all sessions where ended_at IS NULL (currently open).

        Returns:
            List of dicts with keys: ``id``, ``started_at``, ``ended_at``,
            ``agent_id``, ``metadata``.  May be empty.
        zpSELECT id, started_at, ended_at, agent_id, metadata FROM sessions WHERE ended_at IS NULL ORDER BY started_at ASCcursor_factoryN)	r   r"   r   extrasRealDictCursorr#   fetchallr   dict)r   r(   r   r*   rowsrows         r   get_active_sessionsz SessionStore.get_active_sessions|   s    ' 	 }}	 HOO,J,JK &sC ||~& MM$%)*cS	**& & MM$*s(   *B "B B =B4BB B1c                V   d}| j                         }	 |j                  t        j                  j                        5 }|j                  ||f       |j                         }ddd       | j                  |       yt        |      S # 1 sw Y   (xY w# | j                  |       w xY w)a  Fetch a single session by its primary key.

        Args:
            session_id: UUID string of the session to retrieve.

        Returns:
            A dict with keys ``id``, ``started_at``, ``ended_at``,
            ``agent_id``, ``metadata``, or ``None`` if not found.
        zUSELECT id, started_at, ended_at, agent_id, metadata FROM sessions WHERE id = %s::uuidr/   N)	r   r"   r   r1   r2   r#   fetchoner   r4   )r   r'   r(   r   r*   r6   s         r   get_sessionzSessionStore.get_session   s    # 	
 }}	 HOO,J,JK %sC*/lln% MM$;Cy% % MM$s"   *B $B	"B 	BB B(c                   d}| j                         }	 |j                         5 }|j                  |       |j                  }ddd       |j	                          | j                  |       S # 1 sw Y   ,xY w# | j                  |       w xY w)a?  Close sessions older than 24 hours that were never explicitly closed.

        Sets ended_at = NOW() for any session where:
          - ended_at IS NULL  (not yet closed), AND
          - started_at < NOW() - INTERVAL '24 hours'  (stale / orphaned)

        Returns:
            The number of sessions updated.
        zhUPDATE sessions SET ended_at = NOW() WHERE ended_at IS NULL AND started_at < NOW() - INTERVAL '24 hours'N)r   r"   r#   rowcountr$   r   )r   r(   r   r*   updated_counts        r   cleanup_orphaned_sessionsz&SessionStore.cleanup_orphaned_sessions   s    < 	
 }}	  -#C  #- KKMMM$- -
 MM$s"   A9 A-A9 -A62A9 9Bc                R    | j                   r| j                   j                          yy)zBShut down the connection pool, closing all underlying connections.N)r   closeallr   s    r   closezSessionStore.close   s    99II  r   )r   r4   returnNone)rB   rC   )N)r%   r   r&   r4   rB   r   )r'   r   rB   rC   )rB   list)r'   r   rB   zOptional[dict])rB   int)__name__
__module____qualname____doc__r   r   r   r+   r-   r7   r:   r>   rA    r   r   r   r   ,   s4    

# 6 $+,4:!r   r   )rI   
__future__r   r    r   r   typingr   r   psycopg2.extraspsycopg2.poolr   rJ   r   r   <module>rO      s0   > #       `! `!r   