
    )iZ                    \   d Z ddlmZ ddlZddlmc m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mZmZ ddlZdZeej0                  vrej0                  j3                  de       dd	lmZmZmZ dd
lmZ ddl m!Z! ddl"m#Z# ddl$m%Z% d\d]dZ&d^d_dZ'd`dadZ(dbdcdZ)dddedZ*	 	 	 	 df	 	 	 	 	 	 	 	 	 	 	 dgdZ+ G d d      Z, G d d      Z- G d d      Z. G d d      Z/ G d d      Z0dhdZ1dhd Z2dhd!Z3dhd"Z4dhd#Z5dhd$Z6dhd%Z7d& Z8dhd'Z9e:d(k(  rddl;Z;ddl<Z<did)Z=d*d+ fd,d- fd.d/ fd0d1 fd2d3 fd4d5 fd6d7 fd8d9 fd:d; fd<d= fd>d? fd@dA fdB e0       j|                  fdC e0       j~                  fdDdE fdFdG fdHdI fdJdK fdLdM fdNdO fdPdQ fdRe8fdSdT fgZ@dZA eBe@      ZCe@D ]  \  ZDZE	  eE         eFdUeD        eAdz  ZA  eFdXeA dYeC dZ       eAeCk(  r	 eFd[       y ej                  d       yy# eG$ r)ZH eFdVeD dWeH         e;j                          Y dZH[HzdZH[Hww xY w)ju  
tests/track_b/test_story_9_08.py

Story 9.08: EpochTier1Trigger — Autonomous Epistemic Updates Post-Epoch

Black Box Tests (BB):
    BB1  apply() with 3 axioms → Tier1EpochResult.kg_axioms_written == 3
    BB2  apply() always runs regardless of PR (no pr_opened parameter needed)
    BB3  tier1_updates.jsonl contains entry with source="nightly_epoch"

White Box Tests (WB):
    WB1  Both knowledge_writer.write() AND tier1_updater.apply_tier1() are called
    WB2  No .py files are modified (Tier 1 = epistemic only — source check)

Additional tests:
    T01  Tier1EpochResult fields map correctly from WriteResult + Tier1Result
    T02  TIER1_LOG_PATH constant is a string ending in tier1_updates.jsonl
    T03  Log entry timestamp is a valid ISO-8601 UTC string
    T04  Log file is created (parent dirs made) when it does not exist
    T05  Multiple apply() calls each append a separate log entry
    T06  core.epoch __init__ exports EpochTier1Trigger, Tier1EpochResult, TIER1_LOG_PATH
    T07  Log entry epoch_id starts with "epoch_" and contains today's date pattern

ALL external I/O is mocked. tmp_path pytest fixture used for log file paths.
    )annotationsN)datetimetimezone)Path)List)	MagicMockpatchz/mnt/e/genesis-systemEpochTier1TriggerTier1EpochResultTIER1_LOG_PATHAxiomArchitectureAnalysis)WriteResult)Tier1Result   c           	     4    t        d| d|ddd| dg      S )zBuild a single test Axiom.epoch_2026_02_25_03d
operationsg333333?saga_)idcontentcategory
confidencesource_saga_idsr   idxr   s     6/mnt/e/genesis-system/tests/track_b/test_story_9_08.py_make_axiomr"   C   s3    s3i( S	*+     c                b    t        d| dz         D cg c]  }t        |d|        c}S c c}w )z/Build a list of *count* distinct Axiom objects.r   zAxiom content r   )ranger"   )countis     r!   _make_axiom_listr(   N   s0    FKAuWXyFYZKAs';<ZZZs   ,c                    t        g g |       S )z1Build a minimal ArchitectureAnalysis for testing.bottlenecksrecommended_fixesscoper   r-   s    r!   _make_analysisr/   S   s     r#   c                T    t               }t        d||       |j                  _        |S )zLReturn a MagicMock EpochKnowledgeWriter whose write() returns a WriteResult.zN/mnt/e/genesis-system/KNOWLEDGE_GRAPH/axioms/genesis_evolution_learnings.jsonl)kg_file_pathqdrant_upsertsjsonl_entries)r   r   writereturn_value)	kg_axiomsr2   mocks      r!   _make_mock_knowledge_writerr8   \   s*    ;D)e%DJJ
 Kr#   c                V    t               }t        dd| |      |j                  _        |S )zTReturn a MagicMock Tier1AutonomousUpdater whose apply_tier1() returns a Tier1Result.r   )kg_entities_addedscars_updatedprompts_updatedrules_updated)r   r   apply_tier1r5   )promptsrulesr7   s      r!   _make_mock_tier1_updaterrA   g   s/    ;D$/	%D! Kr#   c                x    t        | dz        }t        ||      }t        ||      }t        |||      }|||fS )zGBuild an EpochTier1Trigger with mocked dependencies and a tmp log path.tier1_updates.jsonlr6   r2   r?   r@   knowledge_writertier1_updaterlog_path)strr8   rA   r   )	tmp_pathr6   r2   r?   r@   log_filewriterupdatertriggers	            r!   _make_triggerrP   s   sN     8334H(9^\F&weDGG
 FG##r#   c                  (    e Zd ZdZddZddZddZy)TestBB1_KgAxiomsWrittenCountuG   BB1: apply() with 3 axioms → Tier1EpochResult.kg_axioms_written == 3.c                J   t        d      }t               }t        |d      \  }}}|j                  ||      }|j                  }d}||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }
t        j                  d|j                         dz   d	|
iz  }t        t        j                  |            d x}x}	}y )
N   r6   ==z9%(py2)s
{%(py2)s = %(py0)s.kg_axioms_written
} == %(py5)sresultpy0py2py5z"Expected kg_axioms_written=3, got 
>assert %(py7)spy7)r(   r/   rP   applykg_axioms_written
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanation)selfrK   axiomsanalysisrO   _rY   @py_assert1@py_assert4@py_assert3@py_format6@py_format8s               r!   )test_kg_axioms_written_equals_axiom_countzFTestBB1_KgAxiomsWrittenCount.test_kg_axioms_written_equals_axiom_count   s   !!$!#%h!<Avx0'' 	
1 	
'1, 	
 	
'1 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 ( 	
 	
 		 ,- 	
 	
  11I1I0JK	
 	
 	
 	
 	
 	
r#   c                   t        |dd      \  }}}|j                  g t                     }|j                  }d}||k(  }|st	        j
                  d|fd||f      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	)
z6Empty axiom list: writer returns 0, result reflects 0.r   rD   rV   rX   rY   rZ   assert %(py7)sr_   N)rP   r`   r/   ra   rb   rc   rd   re   rf   rg   ri   rj   
rk   rK   rO   rn   rY   ro   rp   rq   rr   rs   s
             r!   *test_kg_axioms_written_zero_for_empty_listzGTestBB1_KgAxiomsWrittenCount.test_kg_axioms_written_zero_for_empty_list   s    %h!ANAr>#34'',1,'1,,,,'1,,,,,,v,,,v,,,',,,1,,,,,,,r#   c                   t        |dd      \  }}}|j                  t        d      t                     }|j                  }d}||k(  }|st        j                  d|fd||f      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	)
z;qdrant_scars_updated comes from WriteResult.qdrant_upserts.   rD   rV   z<%(py2)s
{%(py2)s = %(py0)s.qdrant_scars_updated
} == %(py5)srY   rZ   rv   r_   N)rP   r`   r(   r/   qdrant_scars_updatedrb   rc   rd   re   rf   rg   ri   rj   rw   s
             r!   $test_qdrant_scars_updated_propagatedzATestBB1_KgAxiomsWrittenCount.test_qdrant_scars_updated_propagated   s    %h!ANA/2N4DE**/a/*a////*a//////v///v///*///a///////r#   NrK   r   )__name__
__module____qualname____doc__rt   rx   r}    r#   r!   rR   rR      s    Q	
-0r#   rR   c                       e Zd ZdZddZddZy)TestBB2_AlwaysRunsu=   BB2: apply() always runs — no PR gate, no conditional skip.c                   t        |      \  }}}|j                  t        d      t                     }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)uG   apply() signature takes only (axioms, analysis) — no pr_opened param.rT   5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstancerY   r   r[   py1r\   py4N)rP   r`   r(   r/   r   r   rd   re   rb   rf   rg   ri   rj   )rk   rK   rO   rM   rN   rY   rq   @py_format5s           r!   'test_apply_runs_without_any_pr_argumentz:TestBB2_AlwaysRuns.test_apply_runs_without_any_pr_argument   s    #0#:  /2N4DE&"233333333z333z333333&333&333333"2333"23333333333r#   c                   t        g g d      }t        |dd      \  }}}|j                  g |      }|j                  j	                          |j
                  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dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      d	z  }t        t        j                  |            d
}y
)zBEven with a minimal empty ArchitectureAnalysis, apply() completes.	epistemicr*   r   rE   r   r   rY   r   r   N)r   rP   r`   r4   assert_called_oncer>   r   r   rd   re   rb   rf   rg   ri   rj   )	rk   rK   empty_analysisrO   rM   rN   rY   rq   r   s	            r!   #test_apply_runs_with_empty_analysisz6TestBB2_AlwaysRuns.test_apply_runs_with_empty_analysis   s    -"PRZef#01A#N r>2 	'')..0&"233333333z333z333333&333&333333"2333"23333333333r#   Nr~   )r   r   r   r   r   r   r   r#   r!   r   r      s    G4
4r#   r   c                       e Zd ZdZddZddZy)"TestBB3_LogEntrySourceNightlyEpochzGBB3: tier1_updates.jsonl contains an entry with source='nightly_epoch'.c                x   t        |      \  }}}|j                  t        d      t                      t	        |j
                        }|j                  } |       }|st        j                  d|       dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d x}}|j                         j!                         D cg c]#  }|j#                         s|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                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            d x}
x}}t)        j*                  |	d         }|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|j-                  d            dz   d|iz  }t        t        j                  |            d x}x}x}x}}y c c}w )NrT   zLog file not found at zC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}rL   )r[   r\   r   r   )>=)z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)slenlinesr[   r   py3py6zNo log entries written
>assert %(py8)spy8r   sourcenightly_epochrV   )zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)sentry)r[   r\   r   r   py9z%Expected source='nightly_epoch', got z
>assert %(py11)spy11)rP   r`   r(   r/   r   rI   existsrb   rh   rd   re   rf   rg   ri   rj   	read_text
splitlinesstripr   rc   jsonloadsget)rk   rK   rO   rn   rL   ro   rq   r   lnr   @py_assert2@py_assert5rp   @py_format7@py_format9r   @py_assert8@py_assert7@py_format10@py_format12s                       r!   'test_log_entry_has_source_nightly_epochzJTestBB3_LogEntrySourceNightlyEpoch.test_log_entry_has_source_nightly_epoch   s   %h/A&q)>+;<(()E E EE$:8*"EEEEEEExEEExEEEEEE EEEEEE&.&8&8&:&E&E&GV288:VV5z8Q8zQ888zQ888888s888s88888858885888z888Q888 88888888

58$yy 	
 	
y" 	
o 	
"o5 	
 	
"o 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 " 	
 	
 		 # 	
 	
 		 '6 	
 	
  4EIIh4G3JK	
 	
 	
 	
 	
 	
	 Ws   'N7=N7c                H   t        |dddd      \  }}}|j                  t        d      t                      t	        |j
                        }t        j                  |j                         j                         j                         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}||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}||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}||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)z)Each log entry has the four count fields.rT   r   r6   r2   r?   r@   ra   )in)z%(py1)s in %(py3)sr   )r   r   zassert %(py5)sr]   Nr|   prompt_templates_updatedrules_appended)rP   r`   r(   r/   r   rI   r   r   r   r   r   rb   rc   rg   rd   re   rf   ri   rj   )
rk   rK   rO   rn   rL   r   @py_assert0r   @py_format4rr   s
             r!   (test_log_entry_contains_all_count_fieldszKTestBB3_LogEntrySourceNightlyEpoch.test_log_entry_contains_all_count_fields   s   %h!AWX`abA&q)>+;<(()

8--/557BBDRHI"+"e++++"e+++"++++++e+++e+++++++%.%....%...%................)2)U2222)U222)222222U222U2222222(5((((5(((((((((5(((5(((((((r#   Nr~   )r   r   r   r   r   r   r   r#   r!   r   r      s    Q
)r#   r   c                  8    e Zd ZdZddZddZddZddZddZy)	TestWB1_BothDependenciesCalledzNWB1: Both knowledge_writer.write() AND tier1_updater.apply_tier1() are called.c                    t        d      }t               }t        |      \  }}}|j                  ||       |j                  j                          y NrT   )r(   r/   rP   r`   r4   r   rk   rK   rl   rm   rO   rM   rN   s          r!   %test_knowledge_writer_write_is_calledzDTestWB1_BothDependenciesCalled.test_knowledge_writer_write_is_called   sC    !!$!##0#: fh''')r#   c                    t        d      }t               }t        |      \  }}}|j                  ||       |j                  j                          y r   )r(   r/   rP   r`   r>   r   r   s          r!   (test_tier1_updater_apply_tier1_is_calledzGTestWB1_BothDependenciesCalled.test_tier1_updater_apply_tier1_is_called   sE    !!$!##0#: fh'..0r#   c                   t        d      }t               }t        |      \  }}}|j                  ||       |j                  }|j
                  }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                  |	      dz  }t        j                  d|j                  j
                   d      d	z   d
|iz  }t        t        j                  |            dx}x}x}
}	|j                  }|j
                  }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                  |	      dz  }t        j                  d|j                  j
                   d      d	z   d
|iz  }t        t        j                  |            dx}x}x}
}	y)uJ   Both dependencies are called in a single apply() — not one or the other.rz   r   rV   )zM%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.write
}.call_count
} == %(py7)srM   )r[   r\   r   r_   z knowledge_writer.write() called z times, expected 1z
>assert %(py9)sr   N)zS%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.apply_tier1
}.call_count
} == %(py7)srN   z#tier1_updater.apply_tier1() called )r(   r/   rP   r`   r4   
call_countrb   rc   rd   re   rf   rg   rh   ri   rj   r>   )rk   rK   rl   rm   rO   rM   rN   ro   rq   @py_assert6r   rs   r   s                r!   )test_both_called_in_same_apply_invocationzHTestWB1_BothDependenciesCalled.test_both_called_in_same_apply_invocation   s(   !!$!##0#: fh'|| 	
|&& 	
! 	
&!+ 	
 	
&! 	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 ' 	
 	
 		 +, 	
 	
  /v||/F/F.GGYZ	
 	
 	
 	
 	
 	
 "" 	
"-- 	
 	
-2 	
 	
- 	
 	
	6	
 	
   	
 	
 		  	
 	
 		 # 	
 	
 		 . 	
 	
 		 23 	
 	
  2'2E2E2P2P1QQcd	
 	
 	
 	
 	
 	
r#   c                   t        d      }t               }t        |      \  }}}|j                  ||       |j                  j
                  }|j                  r|j                  d   n|j                  j                  d      }||k(  }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      nd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}	y)zMAxioms list is forwarded to knowledge_writer.write() as first positional arg.rT   r   rl   rV   z%(py0)s == %(py2)spassed_axiomsr[   r\   z=Expected axioms list passed to knowledge_writer.write(), got 
>assert %(py4)sr   N)r(   r/   rP   r`   r4   	call_argsargskwargsr   rb   rc   rd   re   rf   rg   rh   ri   rj   )rk   rK   rl   rm   rO   rM   rn   r   r   ro   @py_format3r   s               r!   &test_axioms_passed_to_knowledge_writerzETestWB1_BothDependenciesCalled.test_axioms_passed_to_knowledge_writer  s3   !!$!#*84fh'LL**	-6^^	q)AQAQAUAUV^A_& 	
 	
} 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
6	
 	
  !' 	
 	
 
	 !' 	
 	
  #%	
 	
 	
 	
 	
r#   c                   t        d      }t        d      }t        |      \  }}}|j                  ||       |j                  j
                  }|j                  r|j                  d   n|j                  j                  d      }||u }	|	st        j                  d|	fd||f      dt        j                         v st        j                  |      rt        j                  |      nd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}	y)zAArchitectureAnalysis is forwarded to tier1_updater.apply_tier1().r   r   r.   r   rm   isz%(py0)s is %(py2)spassed_analysisr   zLArchitectureAnalysis object was not forwarded to tier1_updater.apply_tier1()r   r   N)r(   r/   rP   r`   r>   r   r   r   r   rb   rc   rd   re   rf   rg   rh   ri   rj   )rk   rK   rl   rm   rO   rn   rN   r   r   ro   r   r   s               r!   %test_analysis_passed_to_tier1_updaterzDTestWB1_BothDependenciesCalled.test_analysis_passed_to_tier1_updater  s%   !!$!4+H5Gfh'''11	/8~~)..+9CSCSCWCWXbCc(* 	
 	
( 	
 	
	6	
 	
   	
 	
 		  	
 	
	6	
 	
  #+ 	
 	
 		 #+ 	
 	
  [	
 	
 	
 	
 	
r#   Nr~   )	r   r   r   r   r   r   r   r   r   r   r#   r!   r   r      s    X*1

 
r#   r   c                      e Zd ZdZd Zd Zy)TestWB2_NoPyFilesModifieduE   WB2: No .py files are modified — verify via source code inspection.c                   ddl mc m} |j                  }t	        |d      5 }|j                         }ddd       ddl}|j                  d      }|j                        }| }|s~t        j                  d|       dz   dd	t        j                         v st        j                  |      rt        j                  |      nd	iz  }	t        t        j                   |	            d}y# 1 sw Y   xY w)
zSSource of epoch_tier1_trigger.py must not contain open() calls targeting .py files.r   Nutf-8encodingzopen\s*\([^)]*\.py[^)]*\)zBepoch_tier1_trigger.py contains open() calls targeting .py files: 
>assert not %(py0)sr[   matches)core.epoch.epoch_tier1_triggerepochepoch_tier1_trigger__file__openreadrecompilefindallrb   rh   rd   re   rf   rg   ri   rj   )
rk   modsource_pathfhr   r   py_open_patternr   ro   @py_format2s
             r!   test_no_open_calls_to_py_filesz8TestWB2_NoPyFilesModified.test_no_open_calls_to_py_files.  s    44ll+0 	BWWYF	 	**%AB!))&1{ 	
{ 	
  QQXPYZ	
 	
	6	
 	
   	
 	
 		  	
 	
 	
 	
 	
	 	s   C((C1c                X   ddl mc m} |j                  }t	        |d      5 }|j                         }ddd       j                         D cg c]'  }d|v r!|j                         j                  d      s|) }}|D cg c]	  }d|v s| }}| }|s~t        j                  d|       d	z   d
dt        j                         v st        j                  |      rt        j                  |      ndiz  }	t        t        j                   |	            d}y# 1 sw Y   xY wc c}w c c}w )z1No file write operation should target a .py path.r   Nr   r   z.py)#zfrom zimport zopen(z-Found .py file references in open() context: r   r[   py_write_lines)r   r   r   r   r   r   r   r   
startswithrb   rh   rd   re   rf   rg   ri   rj   )
rk   r   r   r   r   linelines_with_pyr   ro   r   s
             r!   test_no_write_to_py_extensionz7TestWB2_NoPyFilesModified.test_no_write_to_py_extension@  s   44ll+0 	BWWYF	 $..0
}TZZ\%<%<=V%W 
 
 ,9L4GtO$LL!! 	
! 	
  <N;KL	
 	
	6	
 	
  " 	
 	
 		 " 	
 	
 	
 	
 	
	 	
 Ms   D,D" 	D'
D'DN)r   r   r   r   r   r   r   r#   r!   r   r   +  s    O
$
r#   r   c                   t        | dddd      \  }}}|j                  t        d      t                     }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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)zLT01: Tier1EpochResult fields correctly map from WriteResult and Tier1Result.      rz   rT   r   rV   rX   rY   rZ   rv   r_   Nr{   )z@%(py2)s
{%(py2)s = %(py0)s.prompt_templates_updated
} == %(py5)s)z6%(py2)s
{%(py2)s = %(py0)s.rules_appended
} == %(py5)s)rP   r`   r(   r/   ra   rb   rc   rd   re   rf   rg   ri   rj   r|   r   r   )	rK   rO   rn   rY   ro   rp   rq   rr   rs   s	            r!   7test_t01_result_fields_map_from_write_and_tier1_resultsr   Y  s   !MGQ ]]+A.0@AF##(q(#q((((#q((((((6(((6(((#(((q(((((((&&+!+&!++++&!++++++6+++6+++&+++!+++++++**/a/*a////*a//////6///6///*///a///////  %A% A%%%% A%%%%%%6%%%6%%% %%%A%%%%%%%r#   c                X   t        t        t              }|s)t        j                  d      dz   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t        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            d}t        j                  }d} ||      }|st        j                  d	t              d
z   dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}}y)z@T02: TIER1_LOG_PATH is a string ending in 'tier1_updates.jsonl'.zTIER1_LOG_PATH must be a stringz7
>assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r   r   rJ   r   NrC   z;TIER1_LOG_PATH should end with 'tier1_updates.jsonl', got: zL
>assert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.endswith
}(%(py4)s)
}r[   r\   r   r   )r   r   rJ   rb   rh   rd   re   rf   rg   ri   rj   endswith)rK   rq   r   ro   r   r   s         r!    test_t02_tier1_log_path_constantr   j  sc   nc*M*MM,MMMMMMM:MMM:MMMMMMnMMMnMMMMMMcMMMcMMM*MMMMMM"" #8 "#89 9   FnEWX             #    $9    :      r#   c                8   t        |       \  }}}|j                  t        d      t                      t	        |j
                        }t        j                  |j                         j                         j                         d         }|j                  dd      }|s{t        j                  d      dz   ddt        j                         v st        j                   |      rt        j"                  |      ndiz  }t%        t        j&                  |            t)        j*                  |j-                  d	d
            }|j.                  }d}	||	u}
|
st        j0                  d|
fd||	f      dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |	      dz  }t        j                  d      dz   d|iz  }t%        t        j&                  |            dx}x}
}	y)z<T03: Log entry timestamp is a parseable ISO-8601 UTC string.rz   r   	timestamp z!timestamp field must not be emptyz
>assert %(py0)sr[   tsZz+00:00N)is not)z2%(py2)s
{%(py2)s = %(py0)s.tzinfo
} is not %(py5)sdtrZ   z*timestamp must include timezone info (UTC)r^   r_   )rP   r`   r(   r/   r   rI   r   r   r   r   r   r   rb   rh   rd   re   rf   rg   ri   rj   r   fromisoformatreplacetzinforc   )rK   rO   rn   rL   r   r  @py_format1r  ro   rp   rq   rr   rs   s                r!   !test_t03_log_timestamp_is_iso8601r
  r  sE   !(+MGQMM"1%~'78G$$%HJJx))+113>>@DEE	;	#B2222222222222222222			

3 9	:B99NDN9D NNN9DNNNNNN2NNN2NNN9NNNDNNN"NNNNNNNNr#   c                   t        | dz  dz  dz        }t        d      }t        dd      }t        |||      }t        j
                  }|j                  }t        j
                  }|j                  } ||      }	 ||	      }
|
 }|sod	d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |	      t        j                  |
      d	z  }t        t        j                  |            dx}x}x}x}x}	x}
}|j                  t!        d      t#                      t        j
                  }|j$                  } ||      }|st        j&                  d|       dz   d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                  |            dx}x}}y)zHT04: Log file parent dirs are created automatically if they don't exist.deepnestedrC   r   rU   r   rE   rF   zassert not %(py14)s
{%(py14)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.path
}.exists
}(%(py12)s
{%(py12)s = %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.path
}.dirname
}(%(py10)s)
})
}os
nested_log)	r[   r\   r   r]   r_   r   py10py12py14Nz%Log file should have been created at zd
>assert %(py7)s
{%(py7)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.path
}.isfile
}(%(py5)s)
})r[   r\   r   r]   r_   )rJ   r8   rA   r   r  pathr   dirnamerd   re   rb   rf   rg   ri   rj   r`   r(   r/   isfilerh   )rK   r  rM   rN   rO   ro   rq   r   r   @py_assert11@py_assert13@py_assert15@py_format16rs   s                 r!   %test_t04_log_file_parent_dirs_createdr    sU   X&14IIJJ(15F&q:GG ww:w~~:bgg:goo:oj9:~9:::::::::::r:::r:::w:::~::::::b:::b:::g:::o::::::j:::j:::9:::::::::::MM"1%~'7877 7>> >*% %   0
|<                       %    %    &      r#   c                Z   t        |       \  }}}|j                  t        d      t                      |j                  t        d      t                      |j                  t        d      t                      t	        |j
                        }|j                         j                         D cg c]#  }|j                         s|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                  |      dz  }	t        j                   d	t        |             d
z   d|	iz  }
t#        t        j$                  |
            dx}x}}|D ]  }t'        j(                  |      }|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}} yc c}w )zDT05: Each apply() call appends a separate log entry (not overwrite).r   rz   rT   rV   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   r   r   z2Expected 3 log entries after 3 apply() calls, got r   r   Nr   r   )z%(py1)s == %(py4)s)r   r   zassert %(py6)sr   )rP   r`   r(   r/   r   rI   r   r   r   r   rb   rc   rd   re   rf   rg   rh   ri   rj   r   r   )rK   rO   rn   rL   r   r   r   r   rp   r   r   r   r   r   rq   r   s                   r!   3test_t05_multiple_apply_calls_each_append_log_entryr    s   !(+MGQMM"1%~'78MM"1%~'78MM"1%~'78G$$%H"*"4"4"6"A"A"CRBrxxzRXXZRERu:  :?   :                                =SZLI      2

4 X1/1/1111/111111/11111112 Ss   .J(J(c                   ddl m}m}m} |t        u }|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  }t	        j                  d      d	z   d
|iz  }t        t	        j                  |            d}|t        u }|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  }t	        j                  d      d	z   d
|iz  }t        t	        j                  |            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  }t	        j                  d      d	z   d
|iz  }t        t	        j                  |            d}y)zUT06: core.epoch __init__ exports EpochTier1Trigger, Tier1EpochResult, TIER1_LOG_PATH.r   r
   r   r   ETTr   r   z1EpochTier1Trigger not re-exported from core.epochr   r   NTERr   z0Tier1EpochResult not re-exported from core.epochrV   r   TLPr   z.TIER1_LOG_PATH not re-exported from core.epoch)
core.epochr   r   r   rb   rc   rd   re   rf   rg   rh   ri   rj   )rK   r  r  r   ro   r   r   s          r!   test_t06_package_init_exportsr"    sr     ##XXX3#XXXXXX3XXX3XXXXXX#XXX#XXXX%XXXXXXX""VVV3"VVVVVV3VVV3VVVVVV"VVV"VVVV$VVVVVVV. RRR3.RRRRRR3RRR3RRRRRR.RRR.RRRR"RRRRRRRr#   c                   ddl }t        |       \  }}}|j                  t        d      t	                      t        |j                        }t        j                  |j                         j                               }|j                  dd      }|j                  }d} ||      }	|	st        j                  d|      dz   dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      t        j$                  |      t        j$                  |	      d	z  }
t'        t        j(                  |
            dx}x}}	|j+                  d
      }|j,                  } ||      }|st        j                  d|      dz   dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      dt        j                          v st        j"                  |      rt        j$                  |      ndt        j$                  |      dz  }t'        t        j(                  |            dx}}y)zIT07: Log entry epoch_id starts with 'epoch_' and contains a date pattern.r   Nr   epoch_idr  epoch_z*epoch_id should start with 'epoch_', got: zN
>assert %(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.startswith
}(%(py4)s)
}r   z\d{4}_\d{2}_\d{2}z6epoch_id should contain YYYY_MM_DD date pattern, got: zJ
>assert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.search
}(%(py3)s)
}date_patternr[   r\   r   r]   )r   rP   r`   r(   r/   r   rI   r   r   r   r   r   r   rb   rh   rd   re   rf   rg   ri   rj   r   search)rK   r   rO   rn   rL   r   r$  ro   rq   r   r   r&  rp   rr   s                 r!   test_t07_log_epoch_id_formatr)    s    !(+MGQMM"1%~'78G$$%HJJx))+1134EyyR(H x x( (   5XLA                  (    )      ::23L x( (   AM                    (     (    )     r#   c                 L   t         j                  }  | t              }|sddt        j                         v st        j                  t               rt        j                  t               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} }t        j                  t              D ch c]  }|j                   }}h d}||k(  } | st        j                  d| fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }t        j                  d| d|       dz   d|iz  }t        t        j                  |            d} yc c}w )zATier1EpochResult must be a proper dataclass with expected fields.zNassert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.is_dataclass
}(%(py3)s)
}dataclassesr   r'  N>   r   ra   r|   r   rV   r   expectedfield_namesr   z,Tier1EpochResult fields mismatch. Expected: z, got: r   r   )r+  is_dataclassr   rd   re   rb   rf   rg   ri   rj   fieldsnamerc   rh   )ro   rp   rr   fr-  r,  r   r   s           r!   $test_tier1_epoch_result_is_dataclassr2    su   ##5#$455555555;555;555#555555$4555$45555555555#.#5#56F#GHa166HKHH {"  8{                #    #    7xj}U     Is   H!c                   t        d      }t               }t        ||d      }t        j                  t
        dt        d            5  |j                  t        d      t                     }ddd       j                  }d}||k(  }|st        j                  d	|fd
||f      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# 1 sw Y   xY w)z@OSError on log write must be caught and not propagate to caller.r   rU   z./mnt/e/this/path/does/not/exist/readonly.jsonlrF   mkdirzPermission denied)side_effectNrV   rX   rY   rZ   rv   r_   )r8   rA   r   r	   objectr   OSErrorr`   r(   r/   ra   rb   rc   rd   re   rf   rg   ri   rj   )
rK   rM   rN   rO   rY   ro   rp   rq   rr   rs   s
             r!   !test_log_oserror_does_not_surfacer8    s    (15F&(GAG 
dG9L1M	N F/2N4DEF
 ##(q(#q((((#q((((((6(((6(((#(((q(((((((F Fs   
$D??E__main__c                 <    t        t        j                               S N)r   tempfilemkdtempr   r#   r!   _tmpr>    s    H$$&''r#   z&BB1: kg_axioms_written==3 for 3 axiomsc                 D    t               j                  t                     S r;  )rR   rt   r>  r   r#   r!   <lambda>r@    s*    ;W;Y  <D  <D  EI  EK  <L r#   z(BB1: kg_axioms_written==0 for empty listc                 D    t               j                  t                     S r;  )rR   rx   r>  r   r#   r!   r@  r@    s*    =Y=[  >G  >G  HL  HN  >O r#   z$BB1: qdrant_scars_updated propagatedc                 D    t               j                  t                     S r;  )rR   r}   r>  r   r#   r!   r@  r@    s$    9U9W9|9|  ~B  ~D  :E r#   z%BB2: apply() runs without pr argumentc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s    :L:N:v:vw{w}:~ r#   z%BB2: apply() runs with empty analysisc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s    :L:N:r:rswsy:z r#   z)BB3: log entry has source='nightly_epoch'c                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s*    >`>b  ?K  ?K  LP  LR  ?S r#   z#BB3: log entry has all count fieldsc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s*    8Z8\  9F  9F  GK  GM  9N r#   z$WB1: knowledge_writer.write() calledc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s$    9W9Y99  AE  AG  :H r#   z'WB1: tier1_updater.apply_tier1() calledc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@     s*    <Z<\  =F  =F  GK  GM  =N r#   z WB1: both called in same apply()c                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s$    5S5U55  AE  AG  6H r#   z&WB1: axioms passed to knowledge_writerc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s*    ;Y;[  <C  <C  DH  DJ  <K r#   z%WB1: analysis passed to tier1_updaterc                 D    t               j                  t                     S r;  )r   r   r>  r   r#   r!   r@  r@    s*    :X:Z  ;A  ;A  BF  BH  ;I r#   zWB2: no open() to .py fileszWB2: no .py file writesz T01: result fields map correctlyc                 (    t        t                     S r;  )r   r>  r   r#   r!   r@  r@    s    5lmqms5t r#   zT02: TIER1_LOG_PATH constantc                 (    t        t                     S r;  )r   r>  r   r#   r!   r@  r@    s    1QRVRX1Y r#   zT03: log timestamp is ISO-8601c                 (    t        t                     S r;  )r
  r>  r   r#   r!   r@  r@    s    3TUYU[3\ r#   zT04: log parent dirs createdc                 (    t        t                     S r;  )r  r>  r   r#   r!   r@  r@  	  s    1VW[W]1^ r#   z"T05: multiple calls append entriesc                 (    t        t                     S r;  )r  r>  r   r#   r!   r@  r@  
  s    7jkokq7r r#   zT06: __init__ exports correctc                 (    t        t                     S r;  )r"  r>  r   r#   r!   r@  r@    s    2OPTPV2W r#   zT07: epoch_id format correctc                 (    t        t                     S r;  )r)  r>  r   r#   r!   r@  r@    s    1Mdf1U r#   z#EDGE: Tier1EpochResult is dataclassz+EDGE: OSError on log write does not surfacec                 (    t        t                     S r;  )r8  r>  r   r#   r!   r@  r@    s    @abfbh@i r#   z	  [PASS] z	  [FAIL] z: 
/z tests passedz(ALL TESTS PASSED -- Story 9.08 (Track B))r   zRead before build)r    intr   rJ   returnr   )rT   )r&   rV  rW  zList[Axiom])r   )r-   rJ   rW  r   )rT   rT   )r6   rV  r2   rV  rW  r   )r   r   )r?   rV  r@   rV  rW  r   )rT   rT   r   r   )rK   r   r6   rV  r2   rV  r?   rV  r@   rV  rW  z.tuple[EpochTier1Trigger, MagicMock, MagicMock]r~   )rW  r   )Kr   
__future__r   builtinsrd   _pytest.assertion.rewrite	assertionrewriterb   r+  r   r  sysr   r   pathlibr   typingr   unittest.mockr   r	   pytestGENESIS_ROOTr  insertr   r   r   r   core.epoch.axiom_distillerr   core.evolution.meta_architectr   !core.epoch.epoch_knowledge_writerr   'core.evolution.tier1_autonomous_updaterr   r"   r(   r/   r8   rA   rP   rR   r   r   r   r   r   r   r
  r  r  r"  r)  r2  r8  r   	tracebackr<  r>  r   r   testspassedr   totalr0  fnprint	Exceptionexc	print_excexitr   r#   r!   <module>rr     s,  4 #      	 
 '   *  'sxxHHOOA|$ 
 - > 9 ?[
	 $$$ $ 	$
 $ 4$00 0>4 42) )J@
 @
F&
 &
\&"O*2&
S(). z( 
2  4L  	M	3  6O  	P	/  2E  	F	02~	02z{	4  7S  	T	.  1N  	O	/  2H  	I	2  5N  	O	+  .H  	I	1  4K  	L	0  3I  	J	&(A(C(b(bc	"$=$?$]$]^	+-tu	')YZ	)+\]	')^_	-/rs	(*WX	')UV	.0TU	68ij/E4 FJE "b	"DIdV$%aKF	" 
Bvhawm
,-89a P  	"IdV2cU+,I!!	"s   3G==H+H&&H+