1111//! assert d.verdict == "block"
1212//! ```
1313
14- use :: a3s_sentry:: verdict:: { Decision as CoreDecision , EnforceAction as CoreAction , Severity , Tier , Verdict } ;
14+ use :: a3s_sentry:: verdict:: { Decision as CoreDecision , EnforceAction as CoreAction , RiskType as CoreRiskType , Severity , Tier , Verdict } ;
1515use :: a3s_sentry:: Sentry as CoreSentry ;
1616use pyo3:: exceptions:: PyValueError ;
1717use pyo3:: prelude:: * ;
@@ -47,6 +47,26 @@ impl From<&CoreAction> for EnforceAction {
4747 }
4848}
4949
50+ /// Stable risk taxonomy attached to a decision, e.g. category=`systemic_risk`,
51+ /// risk_type=`system`.
52+ #[ pyclass( get_all) ]
53+ #[ derive( Clone ) ]
54+ struct RiskDescriptor {
55+ category : String ,
56+ name : String ,
57+ risk_type : String ,
58+ }
59+
60+ #[ pymethods]
61+ impl RiskDescriptor {
62+ fn __repr__ ( & self ) -> String {
63+ format ! (
64+ "RiskDescriptor(category={:?}, name={:?}, risk_type={:?})" ,
65+ self . category, self . name, self . risk_type
66+ )
67+ }
68+ }
69+
5070/// One tier's conclusion about an event. `verdict` is `"allow"`/`"block"`/`"escalate"`, `tier` is
5171/// `"Rules"`/`"Llm"`/`"Agent"`, `severity` is `"info"`..`"critical"`, and `action` is the concrete
5272/// deny (or `None`).
@@ -58,20 +78,25 @@ struct Decision {
5878 severity : String ,
5979 reason : String ,
6080 action : Option < EnforceAction > ,
81+ risk : Option < RiskDescriptor > ,
6182}
6283
6384#[ pymethods]
6485impl Decision {
6586 fn __repr__ ( & self ) -> String {
6687 format ! (
67- "Decision(verdict={:?}, tier={:?}, severity={:?}, reason={:?}, action={})" ,
88+ "Decision(verdict={:?}, tier={:?}, severity={:?}, reason={:?}, action={}, risk={} )" ,
6889 self . verdict,
6990 self . tier,
7091 self . severity,
7192 self . reason,
7293 match & self . action {
7394 Some ( a) => a. __repr__( ) ,
7495 None => "None" . to_string( ) ,
96+ } ,
97+ match & self . risk {
98+ Some ( r) => r. __repr__( ) ,
99+ None => "None" . to_string( ) ,
75100 }
76101 )
77102 }
@@ -100,6 +125,7 @@ fn tier_str(t: Tier) -> &'static str {
100125 Tier :: Rules => "Rules" ,
101126 Tier :: Llm => "Llm" ,
102127 Tier :: Agent => "Agent" ,
128+ Tier :: Sae => "Sae" ,
103129 }
104130}
105131
@@ -111,6 +137,16 @@ impl From<CoreDecision> for Decision {
111137 severity : severity_str ( d. severity ) . to_string ( ) ,
112138 reason : d. reason ,
113139 action : d. action . as_ref ( ) . map ( EnforceAction :: from) ,
140+ risk : d. risk . map ( |r| RiskDescriptor {
141+ category : r. category ,
142+ name : r. name ,
143+ risk_type : match r. risk_type {
144+ CoreRiskType :: System => "system" ,
145+ CoreRiskType :: Communication => "communication" ,
146+ CoreRiskType :: Atomic => "atomic" ,
147+ }
148+ . to_string ( ) ,
149+ } ) ,
114150 }
115151 }
116152}
@@ -207,6 +243,7 @@ fn a3s_sentry(m: &Bound<'_, PyModule>) -> PyResult<()> {
207243 m. add_class :: < Sentry > ( ) ?;
208244 m. add_class :: < Decision > ( ) ?;
209245 m. add_class :: < EnforceAction > ( ) ?;
246+ m. add_class :: < RiskDescriptor > ( ) ?;
210247 m. add_function ( wrap_pyfunction ! ( tool_exec, m) ?) ?;
211248 m. add_function ( wrap_pyfunction ! ( egress, m) ?) ?;
212249 m. add_function ( wrap_pyfunction ! ( file_access, m) ?) ?;
0 commit comments