
    &i+                        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m	Z	 ddl
Z
ddlZ
ddlZ
e G d d             Z G d	 d
      Zy)u6  Cold Ledger — L4 Postgres read/write client for Genesis persistent context.

Provides a typed, connection-pooled interface over the three Cold Ledger tables:
  - events       : append-only event log per session
  - swarm_sagas  : orchestration saga records with full lifecycle status

Usage::

    from core.storage.cold_ledger import ColdLedger, SwarmSaga

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

    event_id = ledger.write_event("session-uuid", "dispatch_start", {"agent": "forge"})
    events   = ledger.get_events("session-uuid", event_type="dispatch_start")

    saga_id  = ledger.write_saga(SwarmSaga(...))
    saga     = ledger.get_saga(saga_id)

    ledger.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)
  - All writes to the events table are append-only (no UPDATE)
    )annotationsN)	dataclass)datetime)Optionalc                  X    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   ded<   ded<   y)	SwarmSagaz9Typed representation of one row in the swarm_sagas table.strsaga_id
session_iddictorchestrator_daglistproposed_deltaszOptional[dict]resolved_statestatusr   
created_atN)__name__
__module____qualname____doc____annotations__     1/mnt/e/genesis-system/core/storage/cold_ledger.pyr   r   1   s,    CLO""Kr   r   c                      e Zd ZdZddZd ZddZ	 	 	 	 	 	 	 	 ddZ	 	 d	 	 	 	 	 	 	 ddZddZ	dd	Z
dd
ZddZedd       Zy)
ColdLedgeru  Typed Postgres client for the Genesis Cold Ledger (L4 storage layer).

    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     r   __init__zColdLedger.__init__P   s&    (MM00L:KL 		r   c                6    | j                   j                         S )zCReturn a connection from the pool (caller must putconn in finally).)r"   getconnr$   s    r   _acquirezColdLedger._acquireZ   s    yy  ""r   c                :    | j                   j                  |       y)z3Return a connection to the pool without closing it.N)r"   putconn)r$   conns     r   _releasezColdLedger._release^   s    		$r   c           
     p   t        t        j                               }d}| j                         }	 |j	                         5 }|j                  ||||t        j                  |      f       ddd       |j                          | j                  |       |S # 1 sw Y   ,xY w# | j                  |       w xY w)u  Append one event record to the events table.

        The write is strictly append-only — no UPDATE path exists.

        Args:
            session_id:  UUID string of the owning session.
            event_type:  Short type label, e.g. ``"dispatch_start"``.
            payload:     Arbitrary JSON-serialisable dict stored as JSONB.

        Returns:
            The new event's UUID string (UUID4).
        zVINSERT INTO events (id, session_id, event_type, payload) VALUES (%s, %s::uuid, %s, %s)N)
r	   uuiduuid4r*   cursorexecutejsondumpscommitr.   )r$   r   
event_typepayloadevent_idsqlr-   curs           r   write_eventzColdLedger.write_eventf   s    $ tzz|$- 	 }}	  #z:tzz'7JK
 KKMMM$  MM$s#   B" *B+B" BB" "B5Nc                   |d}|||f}nd}||f}| 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 )u  Read events for a session, ordered by creation time ascending.

        Args:
            session_id:  UUID string of the session to query.
            event_type:  Optional filter — only return events of this type.
            limit:       Maximum number of rows to return (default 100).

        Returns:
            List of dicts with keys: ``id``, ``session_id``, ``event_type``,
            ``payload``, ``created_at``.
        NzSELECT id, session_id, event_type, payload, created_at FROM events WHERE session_id = %s::uuid AND event_type = %s ORDER BY created_at ASC LIMIT %szSELECT id, session_id, event_type, payload, created_at FROM events WHERE session_id = %s::uuid ORDER BY created_at ASC LIMIT %scursor_factory)	r*   r2   r!   extrasRealDictCursorr3   fetchallr.   r   )
r$   r   r7   limitr:   paramsr-   r;   rowsrows
             r   
get_eventszColdLedger.get_events   s    " !  !*e4F  !%(F}}	 HOO,J,JK &sC(||~& MM$ &**cS	**& & MM$ +s)   *B- #B!/B- C!B*&B- -C c                H   d}|j                   t        j                  |j                         nd}| j                         }	 |j	                         5 }|j                  ||j                  |j                  t        j                  |j                        t        j                  |j                        ||j                  |j                  f       ddd       |j                          | j                  |       |j                  S # 1 sw Y   6xY w# | j                  |       w xY w)u   Persist a SwarmSaga record to the swarm_sagas table.

        Args:
            saga: Fully populated ``SwarmSaga`` dataclass instance.

        Returns:
            The ``saga.saga_id`` string (unchanged — returned for caller convenience).
        zINSERT INTO swarm_sagas (saga_id, session_id, orchestrator_dag, proposed_deltas,  resolved_state, status, created_at) VALUES (%s::uuid, %s::uuid, %s, %s, %s, %s, %s)N)r   r4   r5   r*   r2   r3   r
   r   r   r   r   r   r6   r.   )r$   sagar:   resolved_jsonr-   r;   s         r   
write_sagazColdLedger.write_saga   s    ? 	 04/B/B/NDJJt**+TX 	 }}	  #

4#8#89

4#7#78% KKMMM$||!  MM$s%   D A<DD DD D!c                t   d}| j                         }	 |j                  t        j                  j                        5 }|j                  ||f       |j                         }ddd       | j                  |       y| j                  t        |            S # 1 sw Y   7xY w# | j                  |       w xY w)zFetch a single saga by its primary key.

        Args:
            saga_id: UUID string of the saga to retrieve.

        Returns:
            A ``SwarmSaga`` dataclass instance, or ``None`` if not found.
        zSELECT saga_id, session_id, orchestrator_dag, proposed_deltas,       resolved_state, status, created_at FROM swarm_sagas WHERE saga_id = %s::uuidr>   N)
r*   r2   r!   r@   rA   r3   fetchoner.   _row_to_sagar   )r$   r
   r:   r-   r;   rF   s         r   get_sagazColdLedger.get_saga   s    ( 	 }}	 HOO,J,JK %sC',lln% MM$;  c++% % MM$s"   *B$ $B"B$ B!B$ $B7c                   d}| j                         }	 |j                  t        j                  j                        5 }|j                  ||f       |j                         }ddd       | j                  |       D cg c]  }| j                  t        |             c}S # 1 sw Y   BxY w# | j                  |       w xY wc c}w )zRetrieve all sagas associated with a session, ordered by creation time.

        Args:
            session_id: UUID string of the owning session.

        Returns:
            List of ``SwarmSaga`` dataclass instances (may be empty).
        zSELECT saga_id, session_id, orchestrator_dag, proposed_deltas,       resolved_state, status, created_at FROM swarm_sagas WHERE session_id = %s::uuid ORDER BY created_at ASCr>   N)
r*   r2   r!   r@   rA   r3   rB   r.   rN   r   )r$   r   r:   r-   r;   rE   rF   s          r   get_sagas_by_sessionzColdLedger.get_sagas_by_session   s    ' 	 }}	 HOO,J,JK &sC*/||~& MM$8<=!!$s),==& & MM$=s(   *B/ $B#"B/ ?!C#B,(B/ /Cc                R    | j                   r| j                   j                          yy)zBShut down the connection pool, closing all underlying connections.N)r"   closeallr)   s    r   closezColdLedger.close  s    99II  r   c           
         d }t        t        | d         t        | d          || d         xs i  || d         xs g  || d         | d   | d   	      S )
zConvert a raw database row dict into a typed SwarmSaga instance.

        Handles JSONB columns that psycopg2 returns as already-parsed Python
        objects (dicts/lists) and also the string-fallback case for tests.
        c                b    | y t        | t        t        f      r| S t        j                  |       S )N)
isinstancer   r   r4   loads)vals    r   _parse_jsonz,ColdLedger._row_to_saga.<locals>._parse_json,  s+    {#d|,
::c?"r   r
   r   r   r   r   r   r   )r
   r   r   r   r   r   r   )r   r	   )rF   rZ   s     r   rN   zColdLedger._row_to_saga$  sr    	# I'3|,-(-?)@AGR',=(>?E2&s+;'<=x=<(
 	
r   )r%   r   returnNone)r[   r\   )r   r	   r7   r	   r8   r   r[   r	   )Nd   )r   r	   r7   zOptional[str]rC   intr[   r   )rI   r   r[   r	   )r
   r	   r[   zOptional[SwarmSaga])r   r	   r[   r   )rF   r   r[   r   )r   r   r   r   r&   r*   r.   r<   rG   rK   rO   rQ   rT   staticmethodrN   r   r   r   r   r   C   s    

# !! ! 	!
 
!L %)	-+-+ "-+ 	-+
 
-+f$L,6><! 
 
r   r   )r   
__future__r   r4   r0   dataclassesr   r   typingr   r!   psycopg2.poolpsycopg2.extrasr   r   r   r   r   <module>re      sL   < #   !      	 	 	"x
 x
r   