Ë
    ÛJêj<  ã                  ó†  — 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mZ ddlmZ ddlZddlmZ ddlmZmZmZ ddlmZmZmZmZ  ej6                  «       d"d	„«       Zd#d
„Zd$d„Zd%d„Zd%d„Z d%d„Z!d%d„Z"d%d„Z#d%d„Z$d%d„Z%d%d„Z&d%d„Z'd%d„Z(d%d„Z)d%d„Z*d%d„Z+d%d„Z,d%d„Z-d%d„Z.d%d„Z/d&d„Z0d&d„Z1d&d„Z2d&d „Z3d&d!„Z4y)'u   Tests for the audit hash chain.

Every test here must FAIL when the control it names stops working. A green suite
that cannot go red proves nothing, which is the failure mode this file exists to
avoid â€” it is the same rule the mutation harness in `scripts/` applies to the F4
controls.
é    )ÚannotationsN)ÚPath)ÚAny©ÚSQLiteDatabase)ÚGENESISÚAuditEventTypeÚAuditLog)ÚTamperClassÚcanonical_event_bytesÚcompute_event_hashÚverify_chainc                ó   — t        | dz  «      S )Nzaudit.sqlite3r   )Útmp_paths    ú8/home/oem/CyberBrain26-unified/tests/test_audit_chain.pyÚdbr   $   s   € ä˜( _Ñ4Ó5Ð5ó    c                ó´   — i dd| d›�“dd“dd| z   “dd	“d
d	“dd“dd“dd“dd| › �“dd“dd“dd“dd“dd“dd“dd“dd“dddddœ¥}|j                  |«       |S )zEA minimal chain-shaped record, so tests read as data not as plumbing.Úevent_idÚaudit_Ú016xÚ
event_typeÚtool_calledÚ	timestampg   @üTÙAÚactorzagent-1Úagent_idÚtask_idztask-1ÚmodelÚ ÚtoolÚactionúrun-ÚresourceÚpolicy_decisionÚapproval_idÚresultÚstartedÚerrorÚresource_usage_jsonz{}Ú
latency_msg        Únetwork_actionÚrecorded)Úmemory_writeÚsecurity_alertÚmetadata_jsonÚstatus)Úupdate)ÚseqÚ	overridesÚbases      r   Ú_rowr5   )   s  € ðØ�f˜S ˜JÐ'ðà�mðð 	�_ sÑ*ðð 	�ð	ð
 	�Iðð 	�8ðð 	�ðð 	�ðð 	�D˜˜�,ðð 	�Bðð 	˜2ðð 	�rðð 	�)ðð 	�ðð 	˜tðð  	�cð!ð" 	˜"ð#ð$ ØØØò+€Dð. 	‡K�K�	ÔØ€Kr   c                ó²   — g }t         }t        d| dz   «      D ]<  }t        |«      }||d<   ||d<   t        ||«      |d<   |d   }|j	                  |«       Œ> |S )z?Build a valid chain of ``count`` records starting from genesis.é   r2   Ú	prev_hashÚ
entry_hash)r   Úranger5   r   Úappend)ÚcountÚrowsÚprevr2   Úrows        r   Ú_chainr@   F   sp   € à!#€DÜ€DÜ�Q˜ ™	Ó"ò ˆÜ�3‹iˆØˆˆE‰
ØˆˆKÑÜ.¨s°DÓ9ˆˆLÑØ�<Ñ ˆØ�‰�CÕðð €Kr   c                 óÆ  — t        dt        j                  dddœ«      ¬«      } t        dt        j                  dddœ«      ¬«      }t        dddœ«      }t        dddœ«      }||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                  |«      rt	        j                  |«      nddœz  }dd|iz  }t        t	        j                  |«      «      ‚d}g }| d   }d}||k7  }	|	}
|	r|d   }d}||k7  }|}
|
sþt	        j
                  d|	fd||f«      t	        j                  |«      t	        j                  |«      dœz  }dd|iz  }|j                  |«       |	r_t	        j
                  dfdf«      t	        j                  |«      t	        j                  |«      dœz  }dd|iz  }|j                  |«       t	        j                  |d«      i z  }dd|iz  }t        t	        j                  |«      «      ‚dx}
x}x}x}	x}x}x}}y)uã   Key order in the source dict must not change the digest.

    Otherwise two semantically identical records hash differently and a clean log
    reads as tampered â€” a false positive that would destroy trust in the signal.
    r7   é   )ÚalphaÚbeta)r/   )rD   rC   )ÚxÚy)rF   rE   ©ú==)z%(py0)s == %(py2)sÚleftÚright)Úpy0Úpy2zassert %(py4)sÚpy4Nr/   r   ©ú!=)z%(py3)s != %(py6)s)Úpy3Úpy6z%(py8)sÚpy8)z%(py11)s != %(py14)s)Úpy11Úpy14z%(py16)sÚpy16r   zassert %(py19)sÚpy19)r5   ÚjsonÚdumpsr   Ú
@pytest_arÚ_call_reprcompareÚ@py_builtinsÚlocalsÚ_should_repr_global_nameÚ	_safereprÚAssertionErrorÚ_format_explanationr;   Ú_format_boolop)ÚaÚbrI   rJ   Ú@py_assert1Ú@py_format3Ú@py_format5Ú@py_assert2Ú@py_assert5Ú@py_assert4Ú@py_assert0Ú@py_assert10Ú@py_assert13Ú@py_assert12Ú@py_format7Ú@py_format9Ú@py_format15Ú@py_format17Ú@py_format18Ú@py_format20s                       r   Ú-test_canonical_bytes_stable_across_dict_orderrt   Y   ss  € ô 	ˆQœdŸj™j°1¸aÑ)@ÓAÔB€AÜˆQœdŸj™j°!¸aÑ)@ÓAÔB€Aô ! q¨qÑ!1Ó2€DÜ!¨°Ñ"2Ó3€Eà�5‰=××Õˆ4�5×××××Ñˆ4××Óˆ4×××××Ò�5××Ó�5××××××ÑÙ@ˆ1ˆ_ÑÐ@ Ð@Ð Ñ#Ô@¨¨/Ñ(:Ð@¸bÐ@Ð(:¸bÑ(@×@×@×@Ð ×@×@Ó@Ð×@×@Ò@ ×@×@×@×@×@×@Õ@Ð(:¸b×@×@Ó@Ð(:×@×@Ò@¸b×@×@×@×@×@×@×@×@×@×@×@×@×@×@r   c            	     ó\  — ddi} t        | «      }ddi}t        |«      }||k(  }|�s&t        j                  d|fd||f«      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œz  }dd	|iz  }t        t        j                  |«      «      ‚d
x} x}x}x}}ddi} t        | «      }ddi}t        |«      }||k7  }|�s&t        j                  d|fd||f«      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œz  }dd	|iz  }t        t        j                  |«      «      ‚d
x} x}x}x}}y
)a  An integral float must hash as an int, so a SQLite round-trip cannot lie.

    SQLite has no distinct int/float storage: a value written as ``5.0`` can read
    back as ``5``. Hashing the raw value would make an untouched record fail
    re-verification.
    Úng      @é   rG   )zP%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py10)s
{%(py10)s = %(py6)s(%(py8)s)
}r   ©rK   rL   rM   rQ   rR   Úpy10úassert %(py12)sÚpy12Ng      @rN   ©zP%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} != %(py10)s
{%(py10)s = %(py6)s(%(py8)s)
})	r   rY   rZ   r[   r\   r]   r^   r_   r`   )rd   Ú@py_assert3Ú@py_assert7Ú@py_assert9rh   Ú@py_format11Ú@py_format13s          r   Ú)test_canonical_normalises_integral_floatsr‚   k   s“  € ð #& s ÐOÔ  Ó,ÐOÀsÈAÀhÐOÔ0EÀhÓ0OÐOÐ,Ð0OÑO×O×OÖOÐ,Ð0O×O×O×O×O×OÑOÔ ×O×OÓOÔ ×O×OÔO ×O×OÒOÐ,×O×O×O×O×OÐOÔ0E×O×OÓOÔ0E×O×OÔOÀh×O×OÒOÐ0O×O×O×O×O×O×O×Oà"% s ÐOÔ  Ó,ÐOÀsÈAÀhÐOÔ0EÀhÓ0OÐOÐ,Ð0OÑO×O×OÖOÐ,Ð0O×O×O×O×O×OÑOÔ ×O×OÓOÔ ×O×OÔO ×O×OÒOÐ,×O×O×O×O×OÐOÔ0E×O×OÓOÔ0E×O×OÔOÀh×O×OÒOÐ0O×O×O×O×O×O×O×OÐOr   c            	     óê  — ddi} t        | «      }ddi}t        |«      }||k7  }|�s&t        j                  d|fd||f«      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œz  }dd	|iz  }t        t        j                  |«      «      ‚d
x} x}x}x}}t        j                  t        ddi«      «      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
)zL``True`` is a subclass of ``int``; normalising it would silently corrupt it.ÚflagTr7   rN   r|   r   rx   rz   r{   N©Úis©z%(py1)s is %(py4)s©Úpy1rM   úassert %(py6)srQ   )r   rY   rZ   r[   r\   r]   r^   r_   r`   rW   Úloads)rd   r}   r~   r   rh   r€   r�   rj   rg   rf   rn   s              r   Ú3test_canonical_does_not_collapse_booleans_into_intsrŒ   w   s.  € à"(¨$ ÐVÔ  Ó0ÐVÈ6ÐSTÈ+ÐVÔ4IÈ+Ó4VÐVÐ0Ð4VÑV×V×VÖVÐ0Ð4V×V×V×V×V×VÑVÔ ×V×VÓVÔ ×V×VÔV ×V×VÒVÐ0×V×V×V×V×VÐVÔ4I×V×VÓVÔ4I×V×VÔVÈ+×V×VÒVÐ4V×V×V×V×V×V×V×VÜ�:‰:Ô+¨V°T¨NÓ;Ó<¸VÑDÐLÈÐLÐDÈÐL×L×LÕLÐDÈ×L×LÓLÐD×L×LÒLÈ×L×L×L×L×L×LÔLr   c            	     óª  — t        d«      } t        | «      }|j                  dt        ddœ«       t	        | «      }t	        |«      }||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                  |«      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}x}}y)uF   The links must not hash themselves â€” that would be self-referential.r7   Ú@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)r2   r8   r9   rG   )zN%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py8)s
{%(py8)s = %(py5)s(%(py6)s)
}r   ÚplainÚ
with_links)rK   r‰   rP   Úpy5rQ   rR   zassert %(py10)sry   N)r5   Údictr1   r   r   rY   rZ   r[   r\   r]   r^   r_   r`   )r�   r�   rg   r~   ri   ro   r€   s          r   Ú/test_chain_columns_are_excluded_from_the_digestr“   }   s  € ä�‹G€EÜ�e“€JØ×Ñ˜a¬gÀXÑNÔOä  Ó'ÐLÔ+@ÀÓ+LÐLÐ'Ð+LÑL×L×LÖLÐ'Ð+L×L×L×L×L×LÑLÔ ×L×LÓLÔ ×L×L×L×L×LÒL ×L×LÓL ×L×LÔLÐ'×L×L×L×L×LÐLÔ+@×L×LÓLÔ+@×L×L×L×L×LÒLÀ×L×LÓLÀ×L×LÔLÐ+L×L×L×L×L×L×LÔLr   c                 ó¸  — t        d«      } t        | t        «      }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  }dd	|iz  }t        t	        j                  |«      «      ‚d
x}x}}d„ |D «       }t        |«      }|s•d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
)zÛPin the serialisation so a library upgrade cannot break re-verification.

    A change in float formatting or key ordering would silently make every stored
    chain fail to verify. The vector fails loudly instead.
    r7   é@   rG   ©z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sÚlenÚdigest©rK   r‰   rP   rQ   úassert %(py8)srR   Nc              3  ó$   K  — | ]  }|d v –— Œ
 y­w)Ú0123456789abcdefN© )Ú.0Úcs     r   ú	<genexpr>z*test_golden_link_vector.<locals>.<genexpr>�   s   è ø€ Ò7¨1ˆqÐ&Ô&Ñ7ùs   ‚z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}Úall)rK   rL   rM   )r5   r   r   r—   rY   rZ   r[   r\   r]   r^   r_   r`   r¡   )
r?   r˜   rg   rh   ri   rn   ro   rd   r}   rf   s
             r   Útest_golden_link_vectorr¢   †   s  € ô ˆq‹'€CÜ ¤WÓ-€FÜˆv‹;Ð˜"Ðˆ;˜"Ñ××Õˆ;˜"×××××ÑŒ3××ÓŒ3×××××Òˆv××Óˆv××Ôˆ;××Ò˜"××××××ÓÙ7°Ô7Ð7Œ3Ð7Ó7Ð7Ñ7×7×7×7×7Õ7Œ3×7×7Ó7Œ3×7×7Ô7Ð7×7×7Ò7Ð7×7×7×7×7×7Ó7r   c                 óf  — t        t        d«      «      } | j                  }d}||u }|s­t        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}||u }|s­t        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}||u }|s­t        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(  }|s­t        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 )Nrw   Tr…   ©z.%(py2)s
{%(py2)s = %(py0)s.intact
} is %(py5)sÚreport©rK   rL   r‘   úassert %(py7)sÚpy7©z2%(py2)s
{%(py2)s = %(py0)s.linkage_ok
} is %(py5)s©z4%(py2)s
{%(py2)s = %(py0)s.integrity_ok
} is %(py5)srG   ©z7%(py2)s
{%(py2)s = %(py0)s.records_checked
} == %(py5)s)r   r@   ÚintactrY   rZ   r[   r\   r]   r^   r_   r`   Ú
linkage_okÚintegrity_okÚrecords_checked©r¥   rd   ri   r}   Ú@py_format6Ú@py_format8s         r   Útest_clean_chain_verifiesr³   —   sÓ  € Üœ& ›)Ó$€Fà�=‰=Ð ˜DÐ ˆ=˜DÐ × × Õ ˆ=˜D× × × × × Ñ ˆ6× × Ó ˆ6× × Ô ˆ=× × Ò ˜D× × × × × × Ó Ø×ÑÐ$ Ð$Ð Ð$×$×$Õ$Ð ×$×$×$×$×$Ñ$ˆ6×$×$Ó$ˆ6×$×$Ô$Ð×$×$Ò$ ×$×$×$×$×$×$Ó$Ø×ÑÐ& $Ð&Ð $Ð&×&×&Õ&Ð $×&×&×&×&×&Ñ&ˆ6×&×&Ó&ˆ6×&×&Ô&Ð×&×&Ò& $×&×&×&×&×&×&Ó&Ø×!Ñ!Ð& QÐ&Ð! QÑ&×&×&Õ&Ð! Q×&×&×&×&×&Ñ&ˆ6×&×&Ó&ˆ6×&×&Ô&Ð!×&×&Ò& Q×&×&×&×&×&×&Ô&r   c                 ó(  — t        d«      } d| d   d<   t        | «      }|j                  }d}||u }|s­t        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}||u }|s­t        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                  }t        j                  }||u }|söt        j                  d|fd||f«      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  }dd|iz  }	t        t        j                  |	«      «      ‚d x}x}}|j                  }d}||k(  }|s­t        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 )Né   Útamperedr7   r&   Fr…   r¤   r¥   r¦   r§   r¨   rª   ©zW%(py2)s
{%(py2)s = %(py0)s.tamper_class
} is %(py6)s
{%(py6)s = %(py4)s.IN_PLACE_EDIT
}r   ©rK   rL   rM   rQ   rš   rR   rB   rG   ©z8%(py2)s
{%(py2)s = %(py0)s.first_broken_seq
} == %(py5)s)r@   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   r®   Útamper_classr   ÚIN_PLACE_EDITÚfirst_broken_seq©
r=   r¥   rd   ri   r}   r±   r²   rh   rn   ro   s
             r   Ú test_chain_detects_in_place_editr¾       s  € Ü�!‹9€DØ"€Dˆ�GˆHÑä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ' %Ð'Ð %Ð'×'×'Õ'Ð %×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð×'×'Ò' %×'×'×'×'×'×'Ó'Ø×ÑÐ;¤+×";Ñ";Ð;ÐÐ";Ð;×;×;Õ;ÐÐ";×;×;×;×;×;Ñ;ˆ6×;×;Ó;ˆ6×;×;Ô;Ð×;×;×;×;×;Ð;¤+×;×;Ó;¤+×;×;Ô;Ð";×;×;×;×;×;×;Ó;Ø×"Ñ"Ð' aÐ'Ð" aÑ'×'×'Õ'Ð" a×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð"×'×'Ò' a×'×'×'×'×'×'Ô'r   c                 ó   — d} d}| |z  }t         |k(  }|s¹t        j                  d|fdt         |f«      dt        j                  «       v st        j
                  t         «      rt        j                  t         «      ndt        j                  | «      t        j                  |«      dœz  }dd|iz  }t        t        j                  |«      «      ‚d	x}x} x}}t        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
                  t         «      rt        j                  t         «      ndt        j                  | «      t        j                  |«      dœz  }dd|iz  }t        t        j                  |«      «      ‚d	x} x}}t        t         «      } dh}| |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
                  t         «      rt        j                  t         «      ndt        j                  | «      t        j                  |«      dœz  }dd|iz  }t        t        j                  |«      «      ‚d	x} x}}y	)aG  Pin the anchor value.

    Every writer and every verifier resolves genesis from this constant, so if it
    changed, both sides would move together and every chain would still verify.
    Nothing else in the suite would notice. The anchor is what makes a truncated
    prefix fail, so the value itself has to be asserted.
    Ú0r•   rG   )z%(py0)s == (%(py3)s * %(py5)s)r   )rK   rP   r‘   rš   rR   Nr–   r—   r™   Úset)r   rY   rZ   r[   r\   r]   r^   r_   r`   r—   rÁ   )rg   ri   Ú@py_assert6rd   rn   ro   rh   s          r   Ú test_genesis_is_sixty_four_zerosrÃ   ¬   s�  € ð Ð˜BÐ�c˜B‘hÐŒ7�hÑ××ÕŒ7�h×××××ÑŒ7××ÓŒ7××Ô�c××Ò˜B××××××ÕÜŒw‹<Ð˜2Ðˆ<˜2Ñ××Õˆ<˜2×××××ÑŒ3××ÓŒ3×××××ÒŒw××ÓŒw××Ôˆ<××Ò˜2××××××ÓÜŒw‹<Ð ˜C˜5Ð ˆ<˜5Ñ × × Õ ˆ<˜5× × × × × Ñ Œ3× × Ó Œ3× × × × × Ò Œw× × Ó Œw× × Ô ˆ<× × Ò ˜5× × × × × × Ô r   c                 ó   — t        d«      } | d= t        }| D ]  }||d<   t        ||«      |d<   |d   }Œ | D �cg c]  }|d   ‘Œ	 }}g d¢}||k(  }|slt        j                  d|fd||f«      t        j
                  |«      t        j
                  |«      d	œz  }d
d|iz  }t        t        j                  |«      «      ‚dx}x}}t        | «      }	|	j                  }
d}|
|u }|sÃt        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«      dz   d|iz  }t        t        j                  |«      «      ‚dx}
x}}|	j                  }
t        j                   }|
|u }|söt        j                  d|fd|
|f«      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  }dd|iz  }t        t        j                  |«      «      ‚dx}
x}}yc c}w )uª  A silently bridged chain must still fail â€” via sequence continuity.

    This is the laundering the retention rule forbids: drop a record and relink
    everything after it, so every ``prev_hash`` again matches and the digest of
    every row recomputes. Pointer checking alone passes this. Only a monotonic
    ``seq`` shows that a record went missing â€” and if it does not, a purge can
    quietly launder a finding.
    rµ   r7   r8   r9   r2   )r7   é   rµ   rG   ©z%(py1)s == %(py4)srˆ   rŠ   rQ   NFr…   r¤   r¥   r¦   z1a relinked chain that lost a record must not passz
>assert %(py7)sr¨   ©zR%(py2)s
{%(py2)s = %(py0)s.tamper_class
} is %(py6)s
{%(py6)s = %(py4)s.DELETION
}r   r¸   rš   rR   )r@   r   r   rY   rZ   r^   r_   r`   r   r¬   r[   r\   r]   Ú_format_assertmsgrº   r   ÚDELETION)r=   r>   r?   Úrrj   r}   rg   rf   rn   r¥   rd   ri   r±   r²   rh   ro   s                   r   Ú)test_relinked_chain_still_reveals_the_gaprË   ¹   sÒ  € ô �!‹9€DØˆQˆô €DØò !ˆØˆˆKÑÜ.¨s°DÓ9ˆˆLÑØ�<Ñ ‰ð!ð
 #Ö#˜ˆAˆe‹HÐ#Ð0Ð#¢yÐ0Ð# yÑ0×0×0Õ0Ð# y×0×0Ó0Ð#×0×0Ò0 y×0×0×0×0×0×0Ó0ä˜$Ó€Fà�=‰=ÐV˜EÐVˆ=˜EÐ!×V×VÕVˆ=˜E×V×V×V×V×VÑVˆ6×V×VÓVˆ6×V×VÔVˆ=×V×VÒV˜E×V×V×VÐ#V×V×V×V×V×V×VÐVØ×ÑÐ6¤+×"6Ñ"6Ð6ÐÐ"6Ð6×6×6Õ6ÐÐ"6×6×6×6×6×6Ñ6ˆ6×6×6Ó6ˆ6×6×6Ô6Ð×6×6×6×6×6Ð6¤+×6×6Ó6¤+×6×6Ô6Ð"6×6×6×6×6×6×6Ô6ùò $s   ¹Kc                 óŽ  — t        d«      } | d= t        | «      }|j                  }d}||u }|s­t        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}||u }|s­t        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                  }t        j                  }||u }|söt        j                  d|fd||f«      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  }dd|iz  }	t        t        j                  |	«      «      ‚d x}x}}y )Nrµ   r7   Fr…   r¤   r¥   r¦   r§   r¨   r©   rÇ   r   r¸   rš   rR   )r@   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   r­   rº   r   rÉ   r½   s
             r   Útest_chain_detects_deletionrÍ   ×   s•  € Ü�!‹9€DØˆQˆä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ% Ð%Ð Ð%×%×%Õ%Ð ×%×%×%×%×%Ñ%ˆ6×%×%Ó%ˆ6×%×%Ô%Ð×%×%Ò% ×%×%×%×%×%×%Ó%Ø×ÑÐ6¤+×"6Ñ"6Ð6ÐÐ"6Ð6×6×6Õ6ÐÐ"6×6×6×6×6×6Ñ6ˆ6×6×6Ó6ˆ6×6×6Ô6Ð×6×6×6×6×6Ð6¤+×6×6Ó6¤+×6×6Ô6Ð"6×6×6×6×6×6×6Ô6r   c                 ó  — t        d«      } | d   | d   c| d<   | d<   t        | «      }|j                  }d}||u }|s­t        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                  }t        j                  }||u }|söt        j                  d|fd||f«      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  }dd|iz  }	t        t        j                  |	«      «      ‚d x}x}}y )Nrµ   rB   r7   Fr…   r¤   r¥   r¦   r§   r¨   )zT%(py2)s
{%(py2)s = %(py0)s.tamper_class
} is %(py6)s
{%(py6)s = %(py4)s.REORDERING
}r   r¸   rš   rR   )r@   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   rº   r   Ú
REORDERINGr½   s
             r   Útest_chain_detects_reorderingrÐ   â   s8  € Ü�!‹9€DØ˜A‘w  Q¡Ð€Dˆ�GˆT�!‰Wä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ8¤+×"8Ñ"8Ð8ÐÐ"8Ð8×8×8Õ8ÐÐ"8×8×8×8×8×8Ñ8ˆ6×8×8Ó8ˆ6×8×8Ô8Ð×8×8×8×8×8Ð8¤+×8×8Ó8¤+×8×8Ô8Ð"8×8×8×8×8×8×8Ô8r   c                 ó�  — t        d«      dd } t        | «      }|j                  }d}||u }|s­t        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                  }t        j                  }||u }|söt        j                  d|fd||f«      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  }dd|iz  }	t        t        j                  |	«      «      ‚dx}x}}|j                  }d}||k(  }|s­t        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)u:  Removing the first record must break, thanks to the genesis anchor.

    Without genesis an attacker could lop off the head and present the remainder
    as a complete history. The break surfaces at the first *surviving* record,
    which is seq 2 â€” that is where the dangling ``prev_hash`` becomes visible.
    rµ   r7   NFr…   r¤   r¥   r¦   r§   r¨   )z[%(py2)s
{%(py2)s = %(py0)s.tamper_class
} is %(py6)s
{%(py6)s = %(py4)s.PREFIX_TRUNCATION
}r   r¸   rš   rR   rB   rG   r¹   )r@   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   rº   r   ÚPREFIX_TRUNCATIONr¼   r½   s
             r   Ú$test_chain_detects_prefix_truncationrÓ   ì   s–  € ô �!‹9�Q�Rˆ=€Dä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ?¤+×"?Ñ"?Ð?ÐÐ"?Ð?×?×?Õ?ÐÐ"?×?×?×?×?×?Ñ?ˆ6×?×?Ó?ˆ6×?×?Ô?Ð×?×?×?×?×?Ð?¤+×?×?Ó?¤+×?×?Ô?Ð"?×?×?×?×?×?×?Ó?Ø×"Ñ"Ð' aÐ'Ð" aÑ'×'×'Õ'Ð" a×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð"×'×'Ò' a×'×'×'×'×'×'Ô'r   c                 ó\  — t        d«      } d| d   d<   t        | «      }|j                  }d}||u }|s­t        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}||u }|s­t        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ÈA linked chain with an edited field is not the same finding as a broken link.

    Collapsing both into one boolean hides which attack occurred, which is the
    whole reason they are two fields.
    rµ   r¶   r7   r&   Tr…   r©   r¥   r¦   r§   r¨   NFrª   )r@   r   r­   rY   rZ   r[   r\   r]   r^   r_   r`   r®   ©r=   r¥   rd   ri   r}   r±   r²   s          r   Ú2test_linkage_and_integrity_are_reported_separatelyrÖ   ü   s  € ô �!‹9€DØ"€Dˆ�GˆHÑÜ˜$Ó€Fð ×ÑÐ$ Ð$Ð Ð$×$×$Õ$Ð ×$×$×$×$×$Ñ$ˆ6×$×$Ó$ˆ6×$×$Ô$Ð×$×$Ò$ ×$×$×$×$×$×$Ó$Ø×ÑÐ' %Ð'Ð %Ð'×'×'Õ'Ð %×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð×'×'Ò' %×'×'×'×'×'×'Ô'r   c                 óp  — t        d«      } d| d   d<   d| d   d<   t        | «      }|j                  }d}||k(  }|s­t        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(  }|s­t        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·Records after a break inherit an untrustworthy predecessor.

    Reporting every downstream mismatch would overstate the damage: only the
    first divergence is a reliable fact.
    é   r¶   r7   r&   zalso-tamperedrµ   rB   rG   r¹   r¥   r¦   r§   r¨   Nr«   )r@   r   r¼   rY   rZ   r[   r\   r]   r^   r_   r`   r¯   rÕ   s          r   Ú test_verify_stops_at_first_breakrÙ     s  € ô �!‹9€DØ"€Dˆ�GˆHÑØ'€Dˆ�GˆHÑä˜$Ó€Fà×"Ñ"Ð' aÐ'Ð" aÑ'×'×'Õ'Ð" a×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð"×'×'Ò' a×'×'×'×'×'×'Ó'Ø×!Ñ!Ð& QÐ&Ð! QÑ&×&×&Õ&Ð! Q×&×&×&×&×&Ñ&ˆ6×&×&Ó&ˆ6×&×&Ô&Ð!×&×&Ò& Q×&×&×&×&×&×&Ô&r   c                 ól  — t        d«      } d| d   d<   d| d   d<   t        | «      }|j                  }d}||u }|s­t        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}||u }|s­t        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ÀA log that predates the chain must NOT verify as clean.

    Treating "never chained" as a pass is the failure mode where a green result
    proves nothing about history it never covered.
    rÅ   Nr   r9   r8   Fr…   r¤   r¥   r¦   r§   r¨   T)z:%(py2)s
{%(py2)s = %(py0)s.unverified_premise
} is %(py5)s)r@   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   Úunverified_premiserÕ   s          r   Ú!test_unchained_head_is_not_intactrÜ     s  € ô �!‹9€DØ €Dˆ�GˆLÑØ€Dˆ�GˆKÑä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×$Ñ$Ð,¨Ð,Ð$¨Ð,×,×,Õ,Ð$¨×,×,×,×,×,Ñ,ˆ6×,×,Ó,ˆ6×,×,Ô,Ð$×,×,Ò,¨×,×,×,×,×,×,Ô,r   c                 ó8  — t        g «      } | j                  }d}||u }|s­t        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(  }|s­t        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)uL   An empty log is vacuously intact â€” and the report says it checked nothing.Tr…   r¤   r¥   r¦   r§   r¨   Nr   rG   r«   )r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   r¯   r°   s         r   Ú+test_empty_log_is_intact_and_checks_nothingrÞ   ,  së   € ä˜"Ó€Fà�=‰=Ð ˜DÐ ˆ=˜DÐ × × Õ ˆ=˜D× × × × × Ñ ˆ6× × Ó ˆ6× × Ô ˆ=× × Ò ˜D× × × × × × Ó Ø×!Ñ!Ð& QÐ&Ð! QÑ&×&×&Õ&Ð! Q×&×&×&×&×&Ñ&ˆ6×&×&Ó&ˆ6×&×&Ô&Ð!×&×&Ò& Q×&×&×&×&×&×&Ô&r   c                 ó"  — t        d«      } | d   j                  d«       t        | «      }|j                  }d}||u }|s­t	        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                  }t        j                  }||u }|söt	        j
                  d|fd||f«      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  }dd|iz  }	t        t	        j                  |	«      «      ‚dx}x}}y)z;A record with no sequence number has an undefined position.rÅ   r7   r2   Fr…   r¤   r¥   r¦   r§   r¨   N)zV%(py2)s
{%(py2)s = %(py0)s.tamper_class
} is %(py6)s
{%(py6)s = %(py4)s.MISSING_LINK
}r   r¸   rš   rR   )r@   Úpopr   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   rº   r   ÚMISSING_LINKr½   s
             r   Ú(test_missing_seq_is_reported_not_skippedrâ   4  s,  € ä�!‹9€DØˆ�G‡K�K�Ôä˜$Ó€Fà�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ:¤+×":Ñ":Ð:ÐÐ":Ð:×:×:Õ:ÐÐ":×:×:×:×:×:Ñ:ˆ6×:×:Ó:ˆ6×:×:Ô:Ð×:×:×:×:×:Ð:¤+×:×:Ó:¤+×:×:Ô:Ð":×:×:×:×:×:×:Ô:r   c                ó¾  — t        | «      }|j                  t        j                  d¬«       |j                  t        j                  d¬«       |j                  t        j                  d¬«       |j	                  «       }|D �cg c]  }|d   ‘Œ	 }}g d¢}||k(  }|slt        j                  d|fd||f«      t        j                  |«      t        j                  |«      d	œz  }d
d|iz  }t        t        j                  |«      «      ‚dx}x}}|d   d   }|t        k(  }|s¥t        j                  d|fd|t        f«      t        j                  |«      dt        j                  «       v st        j                  t        «      rt        j                  t        «      nddœz  }	dd|	iz  }
t        t        j                  |
«      «      ‚dx}}|d   d   }|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                  } |«       }|j                  }d}||u }|sÕt        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c c}w )zÕEach write continues from the current head rather than from genesis.

    A second producer starting at genesis orphans everything before it, and the
    gap reads as a clean start instead of the break it is.
    rb   ©r!   rc   rŸ   r2   )r7   rB   rÅ   rG   rÆ   rˆ   rŠ   rQ   Nr   r8   )z%(py1)s == %(py3)sr   )r‰   rP   zassert %(py5)sr‘   r7   r9   Tr…   ©zg%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.verify_chain
}()
}.intact
} is %(py9)sÚaudit©rK   rL   rM   rQ   Úpy9úassert %(py11)srS   )r
   Úrecordr	   ÚTOOL_CALLEDÚ
chain_rowsrY   rZ   r^   r_   r`   r   r[   r\   r]   r   r¬   )r   ræ   r=   rÊ   rj   r}   rg   rf   rn   Ú@py_format4r±   rd   rh   Ú@py_assert8r~   Ú@py_format10Ú@py_format12s                    r   Ú"test_append_does_not_restart_chainrñ   D  s*  € ô �R‹L€EØ	‡L�L”×+Ñ+°C€LÔ8Ø	‡L�L”×+Ñ+°C€LÔ8Ø	‡L�L”×+Ñ+°C€LÔ8à×ÑÓ€DØ"Ö#˜ˆAˆe‹HÐ#Ð0Ð#¢yÐ0Ð# yÑ0×0×0Õ0Ð# y×0×0Ó0Ð#×0×0Ò0 y×0×0×0×0×0×0Ó0Ø�‰7�;ÑÐ*Ð¤7Ñ*×*×*Õ*Ð¤7×*×*Ó*Ð×*×*×*×*×*Ð*¤7×*×*Ó*¤7×*×*×*×*×*×*Ó*Ø�‰7�;ÑÐ8 4¨¡7¨<Ñ#8Ð8ÐÐ#8Ñ8×8×8Õ8ÐÐ#8×8×8Ó8Ð×8×8Ò8Ð#8×8×8×8×8×8×8Ó8Ø×ÑÐ.ÑÓÐ.Ð×&Ñ&Ð.¨$Ð.Ð&¨$Ð.×.×.Õ.Ð&¨$×.×.×.×.×.Ñ.ˆ5×.×.Ó.ˆ5×.×.Ô.Ð×.×.Ò.Ð×.×.Ò.Ð&×.×.Ò.¨$×.×.×.×.×.×.×.Ð.ùò $s   ÂMc                óv  — t        | «      }t        d«      D ]&  }|j                  t        j                  d|› �¬«       Œ( |j
                  } |«       }|j                  }d}||u }|sÕt        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                  «       5 }
|
j!                  d«       ddd«       |j                  «       }|j                  }d}||u }|s­t        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"                  }t$        j&                  }||u }|söt        j                  d|fd||f«      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  }dd|iz  }t        t        j                  |«      «      ‚dx}x}}|j(                  }d}||k(  }|s­t        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)zAThe chain must catch an edit made directly in SQL, below the API.rµ   r"   rä   Tr…   rå   ræ   rç   ré   rS   Nz5UPDATE audit_events SET result='tampered' WHERE seq=2Fr¤   r¥   r¦   r§   r¨   r·   r   r¸   rš   rR   rB   rG   r¹   )r
   r:   rê   r	   rë   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   ÚtransactionÚexecuterº   r   r»   r¼   )r   ræ   Úird   r}   rh   rî   r~   rï   rð   Ú
connectionr¥   ri   r±   r²   rn   ro   s                    r   Ú+test_persisted_chain_detects_a_tampered_rowr÷   V  s˜  € ä�R‹L€EÜ�1‹Xò DˆØ�‰”^×/Ñ/¸$¸q¸c¸
ˆÕCðDà×ÑÐ.ÑÓÐ.Ð×&Ñ&Ð.¨$Ð.Ð&¨$Ð.×.×.Õ.Ð&¨$×.×.×.×.×.Ñ.ˆ5×.×.Ó.ˆ5×.×.Ô.Ð×.×.Ò.Ð×.×.Ò.Ð&×.×.Ò.¨$×.×.×.×.×.×.×.à	�‰Ó	ð T˜ZØ×ÑÐRÔS÷Tð ×ÑÓ!€FØ�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!Ø×ÑÐ;¤+×";Ñ";Ð;ÐÐ";Ð;×;×;Õ;ÐÐ";×;×;×;×;×;Ñ;ˆ6×;×;Ó;ˆ6×;×;Ô;Ð×;×;×;×;×;Ð;¤+×;×;Ó;¤+×;×;Ô;Ð";×;×;×;×;×;×;Ó;Ø×"Ñ"Ð' aÐ'Ð" aÑ'×'×'Õ'Ð" a×'×'×'×'×'Ñ'ˆ6×'×'Ó'ˆ6×'×'Ô'Ð"×'×'Ò' a×'×'×'×'×'×'Ô'÷Tñ Tús   ÅP.Ð.P8c                óB  — t        | «      }t        j                  «       dz
  }|j                  t        j                  d¬«       | j                  «       5 }|j                  d|f«       ddd«       |j                  t        j                  d¬«       |j                  «       }|j                  }d}||u }|s­t        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#                  t        j                  «       ¬«       |j%                  «       D �
cg c]  }
|
j&                  dk(  sŒ|
‘Œ }}
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}}|d   }|j,                  }d}||k(  }|s€t        j                  d|fd||f«      t        j                  |«      t        j                  |«      t        j                  |«      dœz  }dd|iz  }t        t        j                   |«      «      ‚dx}x}x}}|d   j.                  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# 1 sw Y   �Œ©xY wc c}
w )#u$  Retention is a deadline and must not be refused â€” but it must not be silent.

    When the chain is already broken, the purge writes a chained event naming what
    it removed. That event cannot be deleted without breaking the chain that
    names it, so the finding outlives the rows.
    é ¬Úoldrä   ú/UPDATE audit_events SET timestamp=? WHERE seq=1Nznew-1Fr…   r¤   Úbeforer¦   r§   r¨   ©ÚnowÚchain_range_purgedr7   rG   r–   r—   Úmarkersr™   zpurge must leave a markerz
>assert %(py8)srR   r   Úpurged_over_unexplained_break©z.%(py3)s
{%(py3)s = %(py1)s.result
} == %(py6)s©r‰   rP   rQ   rš   Úchain_intact_before_purger‡   rˆ   rŠ   rQ   )r
   Útimerê   r	   rë   ró   rô   r   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   Úprune_expiredÚ_load_persistedr!   r—   rÈ   r&   Úmetadata)r   ræ   rú   rö   rü   rd   ri   r}   r±   r²   Úer   rg   rh   rn   ro   rj   rf   s                     r   Ú'test_prune_records_the_range_it_removedr
  f  s¡  € ô �R‹L€EÜ
�)‰)‹+Ð*Ñ
*€CØ	‡L�L”×+Ñ+°E€LÔ:Ø	�‰Ó	ð V˜ZØ×ÑÐLÈsÈfÔU÷Và	‡L�L”×+Ñ+°G€LÔ<à×ÑÓ!€FØ�=‰=Ð!˜EÐ!ˆ=˜EÐ!×!×!Õ!ˆ=˜E×!×!×!×!×!Ñ!ˆ6×!×!Ó!ˆ6×!×!Ô!ˆ=×!×!Ò!˜E×!×!×!×!×!×!Ó!à	×ÑœDŸI™I›KÐÔ(à×/Ñ/Ó1ÖV�Q°Q·X±XÐAUÓ5UŠqÐV€GÐVÜˆw‹<Ð9˜1Ð9ˆ<˜1Ñ×9×9Ö9ˆ<˜1×9×9×9×9×9Ñ9Œ3×9×9Ó9Œ3×9×9×9×9×9Ò9ˆw×9×9Ó9ˆw×9×9Ô9ˆ<×9×9Ò9˜1×9×9×9Ð9×9×9×9×9×9×9Ð9Ø�1‰:Ð?ˆ:×ÑÐ?Ð ?Ð?ÐÐ ?Ñ?×?×?Õ?ÐÐ ?×?×?Ó?ˆ:×?×?Ò?Ð×?×?Ò?Ð ?×?×?×?×?×?×?Õ?Ø�1‰:×ÑÐ:Ñ;ÐD¸uÐDÐ;¸uÐD×D×DÕDÐ;¸u×D×DÓDÐ;×D×DÒD¸u×D×D×D×D×D×DÔD÷Vñ Vüò Ws   ÁPÆPÆ3PÐPc           	     ó>
  — t        | «      }|j                  t        j                  d¬«       |j                  t        j                  d¬«       t	        j                  «       dz
  }| j                  «       5 }|j                  d|f«       ddlm} ddlm	} t        |j                  d	«      j                  «       «      } ||j                  «       D ��ci c]  \  }}||“Œ
 c}}|«      }	|j                  d
||	f«       t        |j                  d«      j                  «       «      }
 ||
j                  «       D ��ci c]  \  }}||“Œ
 c}}|	«      }|j                  d|	|f«       ddd«       |j                  } |«       }|j                  }d}||u }|sÕt        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/                  t	        j                  «       ¬«       |j1                  «       D �cg c]  }|j2                  dk(  sŒ|‘Œ }}t5        |«      }d}||k(  }|söt        j                   d|fd||f«      dt#        j$                  «       v st        j&                  t4        «      rt        j(                  t4        «      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   }|j6                  }d}||k(  }|s€t        j                   d|fd ||f«      t        j(                  |«      t        j(                  |«      t        j(                  |«      d!œz  }dd|iz  }t+        t        j,                  |«      «      ‚dx}x}x}}|d   j8                  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c c}}w c c}}w # 1 sw Y   �Œ¬xY wc c}w )'u1  An unbroken chain purged by retention is recorded as a clean removal.

    Ageing a record means changing its timestamp, and timestamp is hashed â€” so
    the chain has to be relinked afterwards. That is not a contrivance: it is
    exactly what the absence of any "edit a stored record" API is for.
    Úagedrä   Úfreshrù   rû   r   )r   )r   z&SELECT * FROM audit_events WHERE seq=1z=UPDATE audit_events SET prev_hash=?, entry_hash=? WHERE seq=1z&SELECT * FROM audit_events WHERE seq=2z=UPDATE audit_events SET prev_hash=?, entry_hash=? WHERE seq=2NTr…   rå   ræ   rç   ré   rS   rý   rÿ   r7   rG   r–   r—   r   r™   rš   rR   Úpurgedr  r  r  r‡   rˆ   rŠ   rQ   )r
   rê   r	   rë   r  ró   rô   Ú'cyberbrain26.security_plane.audit.chainr   r   r’   ÚfetchoneÚitemsr   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   r  r  r!   r—   r&   r  )r   ræ   rú   rö   ÚGÚcehÚrow1ÚkÚvÚh1Úrow2Úh2rd   r}   rh   rî   r~   rï   rð   r	  r   rg   ri   rn   ro   rj   rf   s                              r   Ú'test_clean_prune_records_a_clean_markerr    s…  € ô �R‹L€EØ	‡L�L”×+Ñ+°F€LÔ;Ø	‡L�L”×+Ñ+°G€LÔ<ä
�)‰)‹+Ð*Ñ
*€CØ	�‰Ó	ð 
˜ZØ×ÑØ=À¸vô	
õ	
õ	
ô �J×&Ñ&Ð'OÓP×YÑYÓ[Ó\ˆÙ 4§:¡:£<×0™4˜1˜a�!�Q‘$Ó0°!Ó4ˆØ×ÑØKÈaÐQSÈWô	
ô �J×&Ñ&Ð'OÓP×YÑYÓ[Ó\ˆÙ 4§:¡:£<×0™4˜1˜a�!�Q‘$Ó0°"Ó5ˆØ×ÑØKÈbÐRTÈXô	
÷'
ð. ×ÑÐ.ÑÓÐ.Ð×&Ñ&Ð.¨$Ð.Ð&¨$Ð.×.×.Õ.Ð&¨$×.×.×.×.×.Ñ.ˆ5×.×.Ó.ˆ5×.×.Ô.Ð×.×.Ò.Ð×.×.Ò.Ð&×.×.Ò.¨$×.×.×.×.×.×.×.à	×ÑœDŸI™I›KÐÔ(à×/Ñ/Ó1ÖV�Q°Q·X±XÐAUÓ5UŠqÐV€GÐVÜˆw‹<Ð˜1Ðˆ<˜1Ñ××Õˆ<˜1×××××ÑŒ3××ÓŒ3×××××Òˆw××Óˆw××Ôˆ<××Ò˜1××××××ÓØ�1‰:Ð(ˆ:×ÑÐ( Ð(Ð Ñ(×(×(Õ(Ð ×(×(Ó(ˆ:×(×(Ò(Ð×(×(Ò( ×(×(×(×(×(×(Õ(Ø�1‰:×ÑÐ:Ñ;ÐC¸tÐCÐ;¸tÐC×C×CÕCÐ;¸t×C×CÓCÐ;×C×CÒC¸t×C×C×C×C×C×CÔCùó# 1ùó
 1÷%
ñ 
üò6 Ws8   Á5ATÃTÃATÄ9TÅTÊ'TÊ<TÔTÔTc           
     ó	  — t        | «      }|j                  t        j                  d¬«       |j                  t        j                  d¬«       |j	                  «        t        | «      }|j
                  } |«       }|j                  }d}||u }|�s2t        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                  |«      t        j                  |«      t        j                  |«      d	œz  }d
d|iz  }	t        t        j                  |	«      «      ‚dx}x}x}x}x}}t        | «      }|j                  } |«       }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                  |«      t        j                  |
«      dœz  }dd|iz  }t        t        j                  |«      «      ‚dx}x}x}x}}
d}g }t         j                  }|j                   }|}|sd}|}||v }|�s'ddt        j                  «       v st        j                  t         «      rt        j                  t         «      ndt        j                  |«      t        j                  |«      dœz  }|j#                  |«       |s+ddt        j                  |«      iz  }|j#                  |«       t        j$                  |d«      i z  }t        j                  d|fd||f«      t        j                  |«      |dœz  }dd|iz  }t        t        j                  |«      «      ‚dx}x}x}x}x}x}}y)uñ   ``clear()`` destroys what the chain exists to protect; that must be stated.

    It cannot be reported afterwards â€” there is nothing left to verify â€” so the
    honest thing is for the method to say so rather than look like a no-op.
    rb   rä   rc   Tr…   )z†%(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py0)s(%(py1)s)
}.verify_chain
}()
}.intact
} is %(py12)sr
   r   )rK   r‰   rP   r‘   r¨   rè   r{   zassert %(py14)srT   Nr   rG   )zc%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py0)s(%(py1)s)
}.count
}()
} == %(py10)s)rK   r‰   rP   r‘   r¨   ry   rz   r{   zNOT a chain operationr   z?%(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s.clear
}.__doc__
})r‘   r¨   rè   z%(py12)sr7   )Úin)z%(py1)s in (%(py15)s))r‰   Úpy15zassert %(py17)sÚpy17)r
   rê   r	   rë   Úclearr   r¬   rY   rZ   r[   r\   r]   r^   r_   r`   r<   Ú__doc__r;   ra   )r   ræ   rg   ri   rÂ   rî   Ú@py_assert11rk   r�   rp   r   r€   rj   r}   rï   Ú@py_format14Ú@py_format16rr   s                     r   Ú.test_clear_is_documented_as_severing_the_chainr$  ¬  s™  € ô �R‹L€EØ	‡L�L”×+Ñ+°C€LÔ8Ø	‡L�L”×+Ñ+°C€LÔ8à	‡K�K„Mä�B‹<Ð5ˆ<×$Ñ$Ð5Ñ$Ó&Ð5Ð&×-Ñ-Ð5°Ð5Ð-°Ð5×5×5Ö5Ð-°×5×5×5×5×5Ñ5Œ8×5×5Ó5Œ8×5×5×5×5×5Ò5�B×5×5Ó5�B×5×5Ô5ˆ<×5×5Ò5Ð$×5×5Ò5Ð&×5×5Ò5Ð-×5×5Ò5°×5×5×5×5×5×5×5Ñ5Ü�B‹<Ð$ˆ<×ÑÐ$ÑÓÐ$ 1Ð$Ð 1Ñ$×$×$Ö$Ð 1×$×$×$×$×$Ñ$Œ8×$×$Ó$Œ8×$×$×$×$×$Ò$�B×$×$Ó$�B×$×$Ô$ˆ<×$×$Ò$Ð×$×$Ò$Ð×$×$Ò$ 1×$×$×$×$×$×$×$Ø"ÒD¤x§~¡~ÐD ~×'=Ñ'=ÐDÐ'=ÐDÑ'=ÀÐDÀÐDÐ"ÐDÐD×D×D×D×D×DÑD¤x×D×DÓD¤x×D×DÔD ~×D×DÒDÐ'=×D×D×DÑDÑ'=×D×DÐDÀ×D×D×D×D×D×D×D×DÔDÐ"×D×DÔDÐ"×D×D×D×D×D×D×DÕDr   )r   r   Úreturnr   )r2   Úintr3   r   r%  zdict[str, Any])r<   r&  r%  zlist[dict[str, Any]])r%  ÚNone)r   r   r%  r'  )5r   Ú
__future__r   Úbuiltinsr[   Ú_pytest.assertion.rewriteÚ	assertionÚrewriterY   rW   r  Úpathlibr   Útypingr   ÚpytestÚ$cyberbrain26.data_plane.sqlite_storer   Ú!cyberbrain26.security_plane.auditr   r	   r
   r  r   r   r   r   Úfixturer   r5   r@   rt   r‚   rŒ   r“   r¢   r³   r¾   rÃ   rË   rÍ   rÐ   rÓ   rÖ   rÙ   rÜ   rÞ   râ   rñ   r÷   r
  r  r$  r�   r   r   ú<module>r3     sÖ   ðñõ #ç  „ ƒÛ Ý Ý ã å ?÷ñ ÷
ó ð €‡�Óò6ó ð6óó:ó&Aó$	PóMóMó	8ó"'ó	(ó
!ó7ó<7ó9ó(ó (ó 'ó -ó 'ó;ó /ó$(ó Eó2*DôZEr   