
    i?f                       d Z ddlmZ ddlZddlmc mZ ddl	Z	e	j                  j                  dd       ddlZddlZddlZddlmZ ddlmZmZmZ ddlZddlmZmZmZ ddlmZ dd	lmZ d
Zddef	 	 	 	 	 	 	 	 	 dTdZdUdVdZdWdZdXdYdZ  G d d      Z! G d d      Z" G d d      Z# G d d      Z$ G d d      Z% G d d      Z& G d d      Z' G d d      Z( G d  d!      Z) G d" d#      Z*e+d$k(  r;ddl,Z,g d% e!       jZ                  fd& e!       j\                  fd' e!       j^                  fd( e!       j`                  fd) e"       jb                  fd* e"       jd                  fd+ e"       jf                  fd, e"       jh                  fd- e"       jj                  fd. e#       jl                  fd/ e#       jn                  fd0 e#       jp                  fd1 e#       jr                  fd2 e#       jt                  fd3 e$       jv                  fd4 e$       jx                  fd5 e$       jz                  fd6 e$       j|                  fd7 e%       j~                  fd8 e%       j                  fd9 e%       j                  fd: e%       j                  fd; e%       j                  fd< e%       j                  fd= e&       j                  fd> e&       j                  fd? e'       j                  fd@ e'       j                  fdA e'       j                  fdB e(       j                  fdC e(       j                  fdD e(       j                  fdE e)       j                  fdF e)       j                  fdG e)       j                  fdH e)       j                  fdI e*       j                  fdJ e*       j                  fZS eTdK       dZUdZVeSD ]  \  ZWZX	  eX         eTdLeW        eUdMz  ZU eUeVz   Z\ eTdPeU dQe\ dR       eVdk(  r	 eTdS       y e	j                  dM       yy# eY$ r.ZZ eTdNeW dOeZ         e,j                          eVdMz  ZVY dZZ[ZdZZ[Zww xY w)Zux  
Tests for Story 5.03 (Track B): EventSourcingStream — Immutable Event Append + Replay

Black Box tests (BB): verify the public contract from the caller's perspective —
    replay returns events in version order, version filtering works,
    state folding is correct, empty sessions return empty/neutral values.

White Box tests (WB): verify internals — dual-write to ColdLedger + Redis,
    Redis failure is non-fatal (event still in ColdLedger), sequential
    application of events in get_current_state.

ALL tests use mocks — NO real Postgres, NO real Redis.

Story: 5.03
File under test: core/storage/event_sourcing.py
    )annotationsNz/mnt/e/genesis-system)datetime)	MagicMockcallpatch)EventSourcingStreamGenesisEventREDIS_STREAM_KEY)r   )r	   z$aaaaaaaa-aaaa-4aaa-baaa-aaaaaaaaaaaastate_updatec                    t        t        t        j                               |||xs dd|  i| t	        dddd| d            S )	z(Build a GenesisEvent with sane defaults.keyvalue_vi        
   r   )id
session_id
event_typepayloadversion
created_at)r	   struuiduuid4r   )r   r   r   r   s       6/mnt/e/genesis-system/tests/track_b/test_story_5_03.py_make_eventr   0   sN     tzz|7EWWI#67D!RWa8     c                    t               }t        t        j                               |j                  _        | | ng |j                  _        |S )z;Return a mock ColdLedger whose get_events returns ``rows``.)r   r   r   r   write_eventreturn_value
get_events)rowsledgers     r   _make_mock_ledgerr$   A   s<    [F&)$**,&7F#-1-=T2F"Mr   c                    | j                   | j                  | j                  j                         d| j                  }t        t        j                               | j                  | j                  || j                  dS )u   Simulate the round-trip through ColdLedger — produce the row that
    get_events would return after append() stored the enriched payload.)event_idr   r   )r   r   r   r   r   )
r   r   r   	isoformatr   r   r   r   r   r   )eventenricheds     r   _event_to_ledger_rowr*   I   sq     HH==&&002 --	H $**,&&&&&& r   c                D    t        |       }t        ||      }||_        |S )z?Return a stream wired to a mock ledger and optional mock redis.cold_ledgerredis_client)r$   r   _mock_ledger)r"   redisr#   streams       r   _make_streamr2   [   s%    t$F V%HF FMr   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestBB1ReplayVersionOrderzFBB1: append 3 events, replay returns all 3 in ascending version order.c                    t        dddi      }t        dddi      }t        dddi      }t        |      t        |      t        |      gS )zQRows returned by get_events in a scrambled order (simulates DB ordering by time).   ar   r      cr   b)r   r*   )selfe1e3e2s       r   _rows_out_of_orderz,TestBB1ReplayVersionOrder._rows_out_of_orderj   sV    S!H5S!H5S!H5 !$ $ $
 	
r   c                   t        | j                               }|j                  t              }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}y )
Nr"   r9   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenresultpy0py1py3py6assert %(py8)spy8)r2   r@   replay
SESSION_IDrF   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr<   r1   rG   @py_assert2@py_assert5@py_assert4@py_format7@py_format9s           r    test_replay_returns_three_eventsz:TestBB1ReplayVersionOrder.test_replay_returns_three_eventsv   s    4#:#:#<=z*6{a{a{ass66{ar   c                   t        | j                               }|j                  t              }|D ]#  }t	        |t
              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t
              rt        j                  t
              ndt        j                  |      dz  }t        t        j                  |            d }& y )NrB   5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceevr	   rI   rJ   py2py4)r2   r@   rO   rP   rb   r	   rS   rT   rQ   rU   rV   rW   rX   )r<   r1   rG   rc   @py_assert3@py_format5s         r   .test_replay_events_are_genesis_event_instanceszHTestBB1ReplayVersionOrder.test_replay_events_are_genesis_event_instances{   s    4#:#:#<=z* 	0Bb,////////:///://////b///b//////,///,//////////	0r   c                   t        | j                               }|j                  t              }|D cg c]  }|j                   }}t        |      }||k(  }|s<t        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       dz   d	|iz  }t        t        j                  |            d x}}g d
}	||	k(  }|st        j                  d|fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      dz  }
dd|
iz  }t        t        j                  |            d x}}	y c c}w )NrB   rC   z0%(py0)s == %(py5)s
{%(py5)s = %(py2)s(%(py3)s)
}versionssortedrI   re   rK   py5z!Expected ascending versions, got z
>assert %(py7)spy7)r6   r   r9   z%(py0)s == %(py3)srI   rK   assert %(py5)sro   )r2   r@   rO   rP   r   rm   rQ   rR   rS   rT   rU   rV   _format_assertmsgrW   rX   )r<   r1   rG   erl   r\   @py_assert1@py_format6@py_format8rZ   @py_format4s              r   (test_replay_ordered_by_version_ascendingzBTestBB1ReplayVersionOrder.test_replay_ordered_by_version_ascending   sI   4#:#:#<=z*'-.!AII..!(+[x++[[[x+[[[[[[x[[[x[[[[[[6[[[6[[[[[[([[[([[[+[[[/PQYPZ-[[[[[[[$$x9$$$$x9$$$$$$x$$$x$$$9$$$$$$$ /s   Ic                    t        g       }|j                  t               |j                  j                  j                  t               y )NrB   )r2   rO   rP   r/   r!   assert_called_once_with)r<   r1   s     r   3test_replay_calls_ledger_get_events_with_session_idzMTestBB1ReplayVersionOrder.test_replay_calls_ledger_get_events_with_session_id   s2    2&j!&&>>zJr   N)	__name__
__module____qualname____doc__r@   r_   ri   rz   r}    r   r   r4   r4   g   s    P

 
0%Kr   r4   c                  4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestBB2ReplayFromVersionzJBB2: replay_from_version returns only events with version >= from_version.c                f    t        dd      D cg c]  }t        t        |             c}S c c}w )Nr6      r   )ranger*   r   )r<   vs     r   
_five_rowsz#TestBB2ReplayFromVersion._five_rows   s'    FKAqkR$[%;<RRRs   .c                   t        | j                               }|j                  t        d      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            d x}x}}y )NrB   r6   from_version   rC   rE   rF   rG   rH   rM   rN   )r2   r   replay_from_versionrP   rF   rQ   rR   rS   rT   rU   rV   rW   rX   rY   s           r   $test_from_version_1_returns_all_fivez=TestBB2ReplayFromVersion.test_from_version_1_returns_all_five   s    4??#45++JQ+G6{a{a{ass66{ar   c                   t        | j                               }|j                  t        d      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            d x}x}}d |D        }t        |      }	|	sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |	      dz  }
t        t        j                  |
            d x}}	y )NrB   r9   r   rC   rE   rF   rG   rH   rM   rN   c              3  :   K   | ]  }|j                   d k\    yw)r9   Nr   ).0ru   s     r   	<genexpr>zMTestBB2ReplayFromVersion.test_from_version_3_returns_three.<locals>.<genexpr>   s     2a199>2s   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}all)rI   re   rf   )r2   r   r   rP   rF   rQ   rR   rS   rT   rU   rV   rW   rX   r   )r<   r1   rG   rZ   r[   r\   r]   r^   rv   rg   rh   s              r   !test_from_version_3_returns_threez:TestBB2ReplayFromVersion.test_from_version_3_returns_three   s   4??#45++JQ+G6{a{a{ass66{a2622s222222222s222s22222222222222r   c                   t        | j                               }|j                  t        d      }t	        |      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            d x}x}}|d   }|j                  }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      t        j                  |      dz  }d
d|iz  }t        t        j                  |            d x}x}x}}y )NrB   r   r   r6   rC   rE   rF   rG   rH   rM   rN   r   )z/%(py3)s
{%(py3)s = %(py1)s.version
} == %(py6)s)rJ   rK   rL   )r2   r   r   rP   rF   rQ   rR   rS   rT   rU   rV   rW   rX   r   )	r<   r1   rG   rZ   r[   r\   r]   r^   @py_assert0s	            r   test_from_version_5_returns_onez8TestBB2ReplayFromVersion.test_from_version_5_returns_one   s   4??#45++JQ+G6{a{a{ass66{aay%y  %A% A%%%% A%%%y%%% %%%A%%%%%%%r   c                   t        | j                               }|j                  t        d      }g }||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d x}}y )
NrB   c   r   rC   rq   rG   rr   rs   ro   )r2   r   r   rP   rQ   rR   rS   rT   rU   rV   rW   rX   r<   r1   rG   rZ   rv   ry   rw   s          r   *test_from_version_beyond_max_returns_emptyzCTestBB2ReplayFromVersion.test_from_version_beyond_max_returns_empty   s    4??#45++JR+Hv|vvvr   c                   t        | j                               }|j                  t        d      }|D cg c]  }|j                   }}t        |      }||k(  }|s#t        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }d	d
|iz  }t        t        j                  |            d x}}y c c}w )NrB   r   r   rC   rk   rl   rm   rn   assert %(py7)srp   )r2   r   r   rP   r   rm   rQ   rR   rS   rT   rU   rV   rW   rX   )	r<   r1   rG   ru   rl   r\   rv   rw   rx   s	            r   $test_result_still_ordered_by_versionz=TestBB2ReplayFromVersion.test_result_still_ordered_by_version   s    4??#45++JQ+G'-.!AII..!(++x+++++x+++++++x+++x++++++6+++6++++++(+++(+++++++++++ /s   FN)
r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r      s$    TS 
3&
,r   r   c                  .    e Zd ZdZd Zd Zd Zd Zd Zy)TestBB3GetCurrentStateFoldingzVBB3: get_current_state folds events; later versions override earlier on key collision.c                t   t        dddi      }t        t        |      g      }|j                  t              }|j
                  }d} ||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      d	z  }	d
d|	iz  }
t        t        j                  |
            d x}x}x}x}}y )Nr6   statusinitialisedr8   rB   rC   zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)sstaterI   re   rf   rL   py9assert %(py11)spy11r   r2   r*   get_current_staterP   getrQ   rR   rS   rT   rU   rV   rW   rX   )r<   rc   r1   r   rv   rg   r[   @py_assert8@py_assert7@py_format10@py_format12s              r   test_single_event_statez5TestBB3GetCurrentStateFolding.test_single_event_state   s    X},EF$8$<#=>((4yy33y"3m3"m3333"m333333u333u333y333333"333m33333333r   c                   t        dddi      }t        dddi      }t        t        |      t        |      g      }|j                  t              }|d   }d}||k(  }|st        j                  d|fd	||f      t        j                  |      t        j                  |      d
z  }t        j                  d      dz   d|iz  }	t        t        j                  |	            d x}x}}y )Nr6   r   r   r8   r   runningrB   rC   z%(py1)s == %(py4)srJ   rf   z!version 2 must override version 1
>assert %(py6)srL   r   r2   r*   r   rP   rQ   rR   rV   rt   rW   rX   )
r<   ev1ev2r1   r   r   rg   rZ   rh   r]   s
             r   test_later_event_overrides_keyz<TestBB3GetCurrentStateFolding.test_later_event_overrides_key   s    !h-FG!h	-BC % %$
  ((4XP)P)+PPP)PPPPPP)PPP-PPPPPPPPr   c                   t        dddi      }t        dddi      }t        t        |      t        |      g      }|j                  t              }|j
                  }d} ||      }d}||k(  }	|	st        j                  d	|	fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d x}x}x}x}	}|j
                  }d} ||      }d}||k(  }	|	st        j                  d	|	fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            d x}x}x}x}	}y )Nr6   agentforger8   r   taskbuildrB   rC   r   r   r   r   r   r   r<   r   r   r1   r   rv   rg   r[   r   r   r   r   s               r    test_non_overlapping_keys_mergedz>TestBB3GetCurrentStateFolding.test_non_overlapping_keys_merged   s   !gw-?@!fg->? % %$
  ((4yy,,y!,W,!W,,,,!W,,,,,,u,,,u,,,y,,,,,,!,,,W,,,,,,,yy++y +G+ G++++ G++++++u+++u+++y++++++ +++G++++++++r   c                6   t        dddd      }t        dddi      }t        dddd	      }t        t        |      t        |      t        |      g
      }|j                  t              }|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d      dz   d|	iz  }
t        t        j                  |
            d x}x}}|d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	t        j                  d      dz   d|	iz  }
t        t        j                  |
            d x}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d x}x}}y )Nr6   alpha)countertagr8   r   r   r9   T)r   donerB   rC   r   r   zFinal version (3) must winr   rL   r   z-Key from version 1 survives if not overriddenr   is)z%(py1)s is %(py4)sassert %(py6)sr   )r<   r   r   ev3r1   r   r   rg   rZ   rh   r]   s              r   %test_three_events_sequential_overridezCTestBB3GetCurrentStateFolding.test_three_events_sequential_override   ss   !7-KL!i^<!D-IJ % % %$
 
 ((4YB1B1$BBB1BBBBBB1BBB&BBBBBBBBU|WwW|w&WWW|wWWW|WWWwWWW(WWWWWWWWV}$$}$$$$}$$$}$$$$$$$$$$r   c                   t        dddi      }t        t        |      g      }|j                  t              }d}||v}|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}d}||v}|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}d}||v}|st        j                  d|fd||f      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	d
z  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}}y)zLInternal keys added by append() must NOT appear in get_current_state output.r6   real_key
real_valuer8   rB   r&   not inz%(py1)s not in %(py3)sr   rJ   rK   z0event_id must be stripped from user-facing state
>assert %(py5)sro   Nr   z/version must be stripped from user-facing stater   z2created_at must be stripped from user-facing state)r   r2   r*   r   rP   rQ   rR   rV   rS   rT   rU   rt   rW   rX   )r<   rc   r1   r   r   rZ   ry   rw   s           r   )test_bookkeeping_keys_stripped_from_statezGTestBB3GetCurrentStateFolding.test_bookkeeping_keys_stripped_from_state   se   Z,FG$8$<#=>((4Zz&ZZZzZZZzZZZZZZZZZZZZZ(ZZZZZZZXy%XXXyXXXyXXXXXXXXXXXXX'XXXXXXX^|5(^^^|5^^^|^^^^^^5^^^5^^^^*^^^^^^^r   N)	r~   r   r   r   r   r   r   r   r   r   r   r   r   r      s     `4Q	,%_r   r   c                  (    e Zd ZdZd Zd Zd Zd Zy)TestBB4EmptySessionuD   BB4: unknown/empty session → neutral return values, no exceptions.c                   t        g       }|j                  t              }g }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}y )NrB   rC   rq   rG   rr   rs   ro   )r2   rO   rP   rQ   rR   rS   rT   rU   rV   rW   rX   r   s          r   $test_replay_empty_returns_empty_listz8TestBB4EmptySession.test_replay_empty_returns_empty_list   sr    2&z*v|vvvr   c                   t        g       }|j                  t        d      }g }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}}y )
NrB   r6   r   rC   rq   rG   rr   rs   ro   )r2   r   rP   rQ   rR   rS   rT   rU   rV   rW   rX   r   s          r   1test_replay_from_version_empty_returns_empty_listzETestBB4EmptySession.test_replay_from_version_empty_returns_empty_list   sy    2&++JQ+Gv|vvvr   c                   t        g       }|j                  t              }i }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}y )NrB   rC   rq   r   rr   rs   ro   )r2   r   rP   rQ   rR   rS   rT   rU   rV   rW   rX   )r<   r1   r   rZ   rv   ry   rw   s          r   /test_get_current_state_empty_returns_empty_dictzCTestBB4EmptySession.test_get_current_state_empty_returns_empty_dict  st    2&((4u{uuur   c                    t        g       }	 |j                  t               |j                  t        d       |j	                  t               y # t
        $ r"}t        j                  d|        Y d }~y d }~ww xY w)NrB   r6   z#Empty session raised unexpectedly: )r2   rO   rP   r   r   	Exceptionpytestfail)r<   r1   excs      r   "test_no_exception_on_empty_sessionz6TestBB4EmptySession.test_no_exception_on_empty_session  se    2&	EMM*%&&z15$$Z0 	EKK=cUCDD	Es   A A 	A:A55A:N)r~   r   r   r   r   r   r   r   r   r   r   r   r      s    N


Er   r   c                  4    e Zd ZdZd Zd Zd Zd Zd Zd Z	y)	TestWB1DualWritezCWB1: append() writes to ColdLedger AND publishes to Redis via XADD.c                    t               }t        |      }t        d      }|j                  |       |j                  j                          y )Nr-   r6   r   r$   r   r   appendr   assert_called_oncer<   mock_ledgerr1   rc   s       r   $test_append_calls_ledger_write_eventz5TestWB1DualWrite.test_append_calls_ledger_write_event  s<    ')$=#b224r   c                   t               }t        |      }t        dd      }|j                  |       |j                  j
                  }|\  }}|j                  d      xs |d   }|j                  d      xs |d   }|t        k(  }	|	st        j                  d|	fd	|t        f      d
t        j                         v st        j                  |      rt        j                  |      nd
dt        j                         v st        j                  t              rt        j                  t              nddz  }
dd|
iz  }t        t        j                  |            d }	d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}	}y )Nr   r6   task_complete)r   r   r   r   r   rC   z%(py0)s == %(py2)spassed_sessionrP   rI   re   assert %(py4)srf   rq   passed_typerr   rs   ro   )r$   r   r   r   r   	call_argsr   rP   rQ   rR   rS   rT   rU   rV   rW   rX   )r<   r   r1   rc   call_kwargsargskwargsr   r   rv   @py_format3rh   rZ   ry   rw   s                  r   5test_append_passes_correct_session_and_type_to_ledgerzFTestWB1DualWrite.test_append_passes_correct_session_and_type_to_ledger  s5   ')$=?b!--77"fL1<T!Wjj.9$q'++++~++++++~+++~++++++++++++++++--{o----{o------{---{---o-------r   c                    t               }t               }t        ||      }t        d      }|j	                  |       |j
                  j                          y )Nr,   r6   r   )r   r$   r   r   r   xaddr   r<   
mock_redisr   r1   rc   s        r   test_append_calls_redis_xaddz-TestWB1DualWrite.test_append_calls_redis_xadd,  sC    [
')$:V#b**,r   c                z   t               }t               }t        ||      }t        d      }|j	                  |       |j
                  j                  }|d   d   }|t        k(  }|st        j                  d|fd|t        f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndd	z  }d
d|iz  }	t        t        j                  |	            d }y )Nr,   r6   r   r   rC   r   
stream_keyr
   r   r   rf   )r   r$   r   r   r   r   r   r
   rQ   rR   rS   rT   rU   rV   rW   rX   )
r<   r   r   r1   rc   	xadd_callr  rv   r   rh   s
             r   'test_redis_xadd_uses_correct_stream_keyz8TestWB1DualWrite.test_redis_xadd_uses_correct_stream_key4  s    [
')$:V#bOO--	q\!_
-----z-------z---z------------------r   c                   t               }t               }t        ||      }t        d      }|j	                  |       |j
                  j                  }|d   d   }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndd	z  }	d
d|	iz  }
t        t        j                  |
            d x}}|d   }|j                  }||k(  }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }
dd|
iz  }t        t        j                  |            d x}x}}y )Nr,   r6   r   r   r&   inz%(py1)s in %(py3)smessage_dictr   rs   ro   rC   )z*%(py1)s == %(py5)s
{%(py5)s = %(py3)s.id
}rc   )rJ   rK   ro   r   rp   )r   r$   r   r   r   r   r   rQ   rR   rV   rS   rT   rU   rW   rX   r   )r<   r   r   r1   rc   r  r	  r   rZ   ry   rw   r\   rx   s                r   )test_redis_xadd_message_contains_event_idz:TestWB1DualWrite.test_redis_xadd_message_contains_event_id>  s   [
')$:V#bOO--	 |A)z\))))z\)))z))))))\)))\)))))))J'02550'50000'5000'0000002000200050000000r   c                    t               }t        |d      }t        d      }|j                  |       |j                  j                          y)z7When redis_client is None, no Redis interaction occurs.Nr,   r6   r   r   r   s       r   test_no_redis_no_xadd_calledz-TestWB1DualWrite.test_no_redis_no_xadd_calledI  s>    ')$4P#b224r   N)
r~   r   r   r   r   r   r   r  r
  r  r   r   r   r   r     s#    M5.-.	15r   r   c                  "    e Zd ZdZd Zd Zd Zy)TestWB2RedisFailureNonFatalzNWB2: Redis XADD failure is logged and does NOT prevent ColdLedger persistence.c                    t               }t        d      |j                  _        t	               }t        ||      }t        d      }	 |j                  |       y # t        $ r t        j                  d       Y y w xY w)NzRedis unreachabler,   r6   r   z-append() must not raise when Redis XADD fails)
r   ConnectionErrorr   side_effectr$   r   r   r   r   r   r   s        r   !test_redis_failure_does_not_raisez=TestWB2RedisFailureNonFatal.test_redis_failure_does_not_raiseZ  si    [
&56I&J
#')$:V#	IMM" 	IKKGH	Is   	A A<;A<c                    t               }t        d      |j                  _        t	               }t        ||      }t        d      }|j                  |       |j                  j                          y )Ntimeoutr,   r6   r   )
r   RuntimeErrorr   r  r$   r   r   r   r   r   r   s        r   /test_ledger_write_still_called_when_redis_failszKTestWB2RedisFailureNonFatal.test_ledger_write_still_called_when_redis_failsf  sV    [
&29&=
#')$:V#b224r   c                   t               }t        d      |j                  _        t	               }t        ||      }t        d      }|j                  t        j                  d      5  |j                  |       d d d        |j                  }t        |      }d}||k\  }	|	s t        j                  d|	fd||f      d	t        j                          v st        j"                  t              rt        j$                  t              nd	d
t        j                          v st        j"                  |      rt        j$                  |      nd
t        j$                  |      t        j$                  |      t        j$                  |      dz  }
t        j&                  d      dz   d|
iz  }t)        t        j*                  |            d x}x}x}	}|j                  d   }|j,                  }t        j                  }	||	k(  }|st        j                  d|fd||	f      t        j$                  |      t        j$                  |      dt        j                          v st        j"                  t              rt        j$                  t              ndt        j$                  |	      dz  }dd|iz  }t)        t        j*                  |            d x}x}x}}	y # 1 sw Y   WxY w)Nzconnection resetr,   r6   r   zcore.storage.event_sourcing)logger)>=)zM%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.records
})
} >= %(py8)srF   caplog)rI   rJ   rK   ro   rN   z-A log record must be emitted on Redis failurez
>assert %(py10)spy10rC   )zJ%(py3)s
{%(py3)s = %(py1)s.levelno
} == %(py7)s
{%(py7)s = %(py5)s.ERROR
}logging)rJ   rK   ro   rp   zassert %(py9)sr   )r   r   r   r  r$   r   r   at_levelr  ERRORr   recordsrF   rQ   rR   rS   rT   rU   rV   rt   rW   rX   levelno)r<   r  r   r   r1   rc   rZ   r\   r   @py_assert6r^   @py_format11r   rx   r   s                  r   test_redis_failure_is_loggedz8TestWB2RedisFailureNonFatal.test_redis_failure_is_loggedp  s   [
&/0B&C
#')$:V#__W]]3P_Q 	MM"	>>Xs>"XaX"a'XXX"aXXXXXXsXXXsXXXXXX6XXX6XXX>XXX"XXXaXXX)XXXXXXXX~~b!:!)):W]]:)]::::)]:::!:::)::::::W:::W:::]:::::::	 	s   )KKN)r~   r   r   r   r  r  r$  r   r   r   r  r  W  s    X
I5	;r   r  c                  "    e Zd ZdZd Zd Zd Zy)TestWB3SequentialApplicationzEWB3: get_current_state applies events one at a time in version order.c                   t        dddd      }t        dddi      }t        t        |      t        |      g	      }|j                  t              }|d   }d}||k(  }|slt        j                  d
|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}|j                  }
d} |
|      }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}
x}x}x}}y)u?   version 1 sets key=A, version 2 sets key=B — final must be B.r6   AT)r   
only_in_v1r8   r   r   BrB   rC   r   r   r   rL   Nr)  r   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} is %(py9)sr   r   zKey set only in v1 must survive
>assert %(py11)sr   r   r2   r*   r   rP   rQ   rR   rV   rW   rX   r   rS   rT   rU   rt   )r<   r   r   r1   r   r   rg   rZ   rh   r]   rv   r[   r   r   r   r   s                   r   +test_overwrite_only_after_version_1_appliedzHTestWB3SequentialApplication.test_overwrite_only_after_version_1_applied  sC   !S-MN!eS\: % %$
  ((4U|"s"|s""""|s"""|"""s"""""""yyQQy&Q$Q&$.QQQ&$QQQQQQuQQQuQQQyQQQQQQ&QQQ$QQQ0QQQQQQQQr   c                   t        dddi      }t        dddd      }t        dddi      }t        t        |      t        |      t        |      g      }|j                  t              }|d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}|j                  }d} ||      }d}||k(  }|st        j                  d	|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}x}x}}y)z<Three events accumulate additive keys one version at a time.r6   stepr8   r   r(  )r/  	milestoner9   rB   rC   r   r   r   rL   Nr0  r   r   r   z#milestone set in v2 must survive v3r+  r   r,  )r<   r   r   r   r1   r   r   rg   rZ   rh   r]   rv   r[   r   r   r   r   s                    r   test_intermediate_state_conceptz<TestWB3SequentialApplication.test_intermediate_state_concept  s[   !fa[9!ac-JK!fa[9 % % %$
 
 ((4V}!!}!!!!}!!!}!!!!!!!!!!yySSy%SS%,SSS%SSSSSSuSSSuSSSySSSSSS%SSSSSS.SSSSSSSSr   c                   t        dddi      }t        di       }t        t        |      t        |      g      }|j                  t              }|j
                  }d} ||      }d}||k(  }	|	st        j                  d|	fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      t        j                  |      d
z  }
t        j                  d      dz   d|
iz  }t        t        j                  |            dx}x}x}x}	}y)zHAn event with an empty user payload must not remove existing state keys.r6   	importantdatar8   r   rB   rC   r   r   r   z'Empty payload must not wipe prior stater+  r   N)r   r2   r*   r   rP   r   rQ   rR   rS   rT   rU   rV   rt   rW   rX   r   s               r   &test_empty_payloads_do_not_clear_statezCTestWB3SequentialApplication.test_empty_payloads_do_not_clear_state  s    !k6-BC!R0 % %$
  ((4yyZZy%ZZ%/ZZZ%ZZZZZZuZZZuZZZyZZZZZZ%ZZZZZZ1ZZZZZZZZr   N)r~   r   r   r   r-  r1  r5  r   r   r   r&  r&    s    O
RT	[r   r&  c                  "    e Zd ZdZd Zd Zd Zy)TestGenesisEventDataclasszCGenesisEvent must be a proper dataclass with all 6 required fields.c                \   t        d      }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d }y )Nr6   r   ra   rb   rc   r	   rd   )
r   rb   r	   rS   rT   rQ   rU   rV   rW   rX   )r<   rc   rg   rh   s       r   test_instantiationz,TestGenesisEventDataclass.test_instantiation  s    #"l++++++++z+++z++++++"+++"++++++l+++l++++++++++r   c                   t        d      }d}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}d}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}d	}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}d
}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}d}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}d}t        ||      }|sddt        j                         v st	        j
                  t              rt	        j                  t              nddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            d x}}y )N   r   r   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}hasattrrc   rI   rJ   rK   ro   r   r   r   r   r   )	r   r<  rS   rT   rQ   rU   rV   rW   rX   )r<   rc   rZ   r\   rw   s        r   test_all_six_fields_presentz5TestGenesisEventDataclass.test_all_six_fields_present  s   # wr4        w   w      r   r   4          '(wr<((((((((w(((w((((((r(((r(((<(((((((((('(wr<((((((((w(((w((((((r(((r(((<(((((((((($%wr9%%%%%%%%w%%%w%%%%%%r%%%r%%%9%%%%%%%%%%$%wr9%%%%%%%%w%%%w%%%%%%r%%%r%%%9%%%%%%%%%%'(wr<((((((((w(((w((((((r(((r(((<((((((((((r   c                4   t        d      }|j                  }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d x}}|j                  }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d x}}|j                  }t        |t              }|sd	dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d x}}|j                  }t        |t              }|sd
dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d x}}|j                  }t        |t               }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               ndt        j                  |      dz  }t        t        j                  |            d x}}|j"                  }t        |t$              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t$              rt        j                  t$              ndt        j                  |      dz  }t        t        j                  |            d x}}y )Nr6   r   zMassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.id
}, %(py4)s)
}rb   rc   r   )rI   rJ   rK   rf   rL   zUassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.session_id
}, %(py4)s)
}zUassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.event_type
}, %(py4)s)
}zRassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.payload
}, %(py4)s)
}dictzRassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.version
}, %(py4)s)
}intzUassert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.created_at
}, %(py4)s)
}r   )r   r   rb   r   rS   rT   rQ   rU   rV   rW   rX   r   r   r   r@  r   rA  r   r   )r<   rc   rZ   r[   r]   s        r   test_field_typesz*TestGenesisEventDataclass.test_field_types  s   #%%%z%%%%%%%%%z%%%z%%%%%%"%%%"%%%%%%%%%%%%%%%%%%%%%%%---z---------z---z------"---"--------------------------z---------z---z------"---"-----------------------**+z*d++++++++z+++z++++++"+++"+++*++++++d+++d++++++++++***z*c********z***z******"***"**********c***c**********--2z-22222222z222z222222"222"222-2222222222222222222r   N)r~   r   r   r   r9  r>  rB  r   r   r   r7  r7    s    M,)3r   r7  c                  (    e Zd ZdZd Zd Zd Zd Zy)TestPackageExportszJEventSourcingStream and GenesisEvent must be importable from core.storage.c                   t         t        u }|st        j                  d|fdt         t        f      dt	        j
                         v st        j                  t               rt        j                  t               nddt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d }y )Nr   z%(py0)s is %(py2)sESS_from_pkgr   r   r   rf   )
rG  r   rQ   rR   rS   rT   rU   rV   rW   rX   r<   rv   r   rh   s       r   %test_event_sourcing_stream_importablez8TestPackageExports.test_event_sourcing_stream_importable  sn    22222|2222222|222|222222222222222222r   c                   t         t        u }|st        j                  d|fdt         t        f      dt	        j
                         v st        j                  t               rt        j                  t               nddt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d }y )Nr   rF  GE_from_pkgr	   r   r   rf   )
rK  r	   rQ   rR   rS   rT   rU   rV   rW   rX   rH  s       r   test_genesis_event_importablez0TestPackageExports.test_genesis_event_importable  sj    l****{l******{***{******l***l*******r   c                Z   ddl m} d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d x}}y )
Nr   __all__r   r  r  rO  r   rs   ro   
core.storagerO  rQ   rR   rV   rS   rT   rU   rW   rX   r<   rO  r   rZ   ry   rw   s         r   'test_all_includes_event_sourcing_streamz:TestPackageExports.test_all_includes_event_sourcing_stream  sa    ($/$////$///$////////////////r   c                Z   ddl m} d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d x}}y )
Nr   rN  r	   r  r  rO  r   rs   ro   rP  rR  s         r   test_all_includes_genesis_eventz2TestPackageExports.test_all_includes_genesis_event  s^    ((~((((~(((~((((((((((((((((r   N)r~   r   r   r   rI  rL  rS  rU  r   r   r   rD  rD    s    T3+0)r   rD  c                      e Zd ZdZd Zd Zy)TestNoSQLitez;event_sourcing.py must not import sqlite3 (Genesis Rule 7).c                   dd l }|j                  d      j                         }d}||v}|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      nddz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            d x}}y )Nr   z4/mnt/e/genesis-system/core/storage/event_sourcing.pyzimport sqlite3r   r   sourcer   uH   event_sourcing.py must NOT import sqlite3 — Genesis Rule 7 (no SQLite)r   ro   )pathlibPath	read_textrQ   rR   rV   rS   rT   rU   rt   rW   rX   )r<   rZ  rY  r   rZ   ry   rw   s          r    test_no_sqlite3_import_in_sourcez-TestNoSQLite.test_no_sqlite3_import_in_source  s    B

)+ 	   	
v- 	
 	
v 	
 	
 		   	
 	
	6	
 	
  (. 	
 	
 		 (. 	
 	
  W	
 	
 	
 	
 	
r   c                   dd l mc m} d}t        ||      }| }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d x}x}}y )Nr   sqlite3z9assert not %(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
}r<  modr=  )core.storage.event_sourcingstorageevent_sourcingr<  rS   rT   rQ   rU   rV   rW   rX   )r<   r`  rZ   r\   r"  r]   s         r   $test_sqlite3_not_in_module_namespacez1TestNoSQLite.test_sqlite3_not_in_module_namespace  s    11 )*73	***********7***7******3***3***	**********r   N)r~   r   r   r   r]  rd  r   r   r   rW  rW    s    E
+r   rW  __main__zBB1a: replay returns 3 eventsz'BB1b: events are GenesisEvent instancesz)BB1c: events ordered by version ascendingzBB1d: replay calls get_eventsu   BB2a: from_v=1 → all fiveu   BB2b: from_v=3 → threeu   BB2c: from_v=5 → oneu   BB2d: from_v=99 → emptyzBB2e: result ordered by versionzBB3a: single event statezBB3b: later event overrides keyz!BB3c: non-overlapping keys mergedz&BB3d: three events sequential overridezBB3e: bookkeeping keys strippedu   BB4a: replay empty → []u&   BB4b: replay_from_version empty → []u$   BB4c: get_current_state empty → {}z#BB4d: no exception on empty sessionz%WB1a: append calls ledger write_eventz+WB1b: correct session+type passed to ledgerzWB1c: append calls redis xaddz"WB1d: xadd uses correct stream keyz$WB1e: xadd message contains event_idu   WB1f: no redis → no xaddz"WB2a: redis failure does not raisez)WB2b: ledger write survives redis failurez%WB3a: overwrite only after v1 appliedz WB3b: intermediate state conceptz'WB3c: empty payloads do not clear statezDC: GenesisEvent instantiationzDC: all 6 fields presentzDC: correct field typesz#PKG: EventSourcingStream importablezPKG: GenesisEvent importablez$PKG: __all__ has EventSourcingStreamzPKG: __all__ has GenesisEventzSQLITE: no import in sourcezSQLITE: not in module namespaceu^   Note: WB2c (redis_failure_is_logged) requires pytest caplog — run via pytest for that test.
z	  [PASS] r6   z	  [FAIL] z: 
/z4 tests passed (standalone runner, excl. caplog test)zHALL STANDALONE TESTS PASSED -- Story 5.03 (Track B): EventSourcingStream)
r   rA  r   r   r   zdict | Noner   r   returnr	   )N)r"   list[dict] | Nonerh  r   )r(   r	   rh  r@  )NN)r"   ri  rh  r   )^r   
__future__r   builtinsrS   _pytest.assertion.rewrite	assertionrewriterQ   syspathinsertjsonr  r   r   unittest.mockr   r   r   r   ra  r   r	   r
   rQ  rG  rK  rP   r   r$   r*   r2   r4   r   r   r   r   r  r&  r7  rD  rW  r~   	tracebackr_   ri   rz   r}   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r
  r  r  r  r-  r1  r5  r9  r>  rB  rI  rL  rS  rU  r]  rd  test_groupsprintpassedfailednamefnr   r   	print_exctotalexitr   r   r   <module>r~     s  " #   
 * +     0 0  \ [ < 4 4

 % 	  	
 "$$K $KV ,  ,N4_ 4_vE E@<5 <5F"; ";R&[ &[Z3 3<) ),+ +* z,	(*C*E*f*fg, 
34M4O4~4~, 
56O6Q6z6z{	,
 
)*C*E*y*yz, 
'(@(B(g(gh, 
$%=%?%a%ab, 
"#;#=#]#]^, 
%&>&@&k&kl, 
+,D,F,k,kl, 
$%B%D%\%\], 
+,I,K,j,jk, 
-.K.M.n.no, 
23P3R3x3xy, 
+,I,K,u,uv,  
%&9&;&`&`a!," 
23F3H3z3z{#,$ 
01D1F1v1vw%,& 
/0C0E0h0hi',* 
12B2D2i2ij+,, 
78H8J  9A  9A  	B-,. 
)*:*<*Y*YZ/,0 
./?/A/i/ij1,2 
01A1C1m1mn3,4 
&'7'9'V'VW5,6 
./J/L/n/no7,8 
56Q6S  7D  7D  	E9,: 
12N2P2|2|};,< 
,-I-K-k-kl=,> 
34P4R4y4yz?,B 
*+D+F+Y+YZC,D 
$%>%@%\%\]E,F 
#$=$?$P$PQG,J 
/0B0D0j0jkK,L 
();)=)[)[\M,N 
01C1E1m1mnO,P 
)*<*>*^*^_Q,T 
'(W(WXU,V 
+LN,_,_`W,K^ 

klFF b	DIdV$%aKF	 VOE	BvhawR
ST{XYK v  	IdV2cU+,I!aKF	s   $P33Q&8$Q!!Q&