66
77use a3s_sentry:: {
88 EnforceAction as CoreAction , RiskType as CoreRiskType , Sentry as CoreSentry , Severity , Tier ,
9- Verdict ,
9+ ThroughL2StageStatus as CoreThroughL2StageStatus , Verdict ,
1010} ;
11+ use napi:: { bindgen_prelude:: AsyncTask , Env , Task } ;
1112use napi_derive:: napi;
13+ use std:: sync:: Arc ;
1214
1315#[ napi( object) ]
1416pub struct EnforceAction {
@@ -48,10 +50,41 @@ pub struct EnforceResult {
4850 pub enforced : Option < String > ,
4951}
5052
53+ #[ napi( object) ]
54+ pub struct ThroughL2Result {
55+ pub l1_decision : Decision ,
56+ pub l2_decision : Option < Decision > ,
57+ pub effective_decision : Decision ,
58+ /// `completed` | `escalated`.
59+ pub stage_status : String ,
60+ /// `l1` | `l2` | `sae`, when the effective decision remains escalated.
61+ pub escalation_cause : Option < String > ,
62+ }
63+
5164/// An in-process sentry judge built from one ACL config.
5265#[ napi]
5366pub struct Sentry {
54- inner : CoreSentry ,
67+ inner : Arc < CoreSentry > ,
68+ }
69+
70+ pub struct EvaluateThroughL2Task {
71+ inner : Arc < CoreSentry > ,
72+ event : String ,
73+ }
74+
75+ impl Task for EvaluateThroughL2Task {
76+ type Output = a3s_sentry:: ThroughL2Result ;
77+ type JsValue = ThroughL2Result ;
78+
79+ fn compute ( & mut self ) -> napi:: Result < Self :: Output > {
80+ self . inner
81+ . evaluate_through_l2 ( & self . event )
82+ . ok_or_else ( || napi:: Error :: from_reason ( "event is not parseable" ) )
83+ }
84+
85+ fn resolve ( & mut self , _env : Env , output : Self :: Output ) -> napi:: Result < Self :: JsValue > {
86+ Ok ( to_through_l2_result ( output) )
87+ }
5588}
5689
5790#[ napi]
@@ -60,7 +93,9 @@ impl Sentry {
6093 #[ napi( factory) ]
6194 pub fn create ( config : String ) -> napi:: Result < Sentry > {
6295 CoreSentry :: create ( & config)
63- . map ( |inner| Sentry { inner } )
96+ . map ( |inner| Sentry {
97+ inner : Arc :: new ( inner) ,
98+ } )
6499 . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) )
65100 }
66101
@@ -70,6 +105,15 @@ impl Sentry {
70105 self . inner . evaluate ( & event) . map ( to_decision)
71106 }
72107
108+ /// Judge through L2 on the napi worker pool, preserving escalation for an external L3 worker.
109+ #[ napi]
110+ pub fn evaluate_through_l2 ( & self , event : String ) -> AsyncTask < EvaluateThroughL2Task > {
111+ AsyncTask :: new ( EvaluateThroughL2Task {
112+ inner : Arc :: clone ( & self . inner ) ,
113+ event,
114+ } )
115+ }
116+
73117 /// Judge and, on a block carrying a target, write it to the configured deny-file. `null` if the
74118 /// event isn't parseable.
75119 #[ napi]
@@ -83,6 +127,27 @@ impl Sentry {
83127 }
84128}
85129
130+ fn to_through_l2_result ( result : a3s_sentry:: ThroughL2Result ) -> ThroughL2Result {
131+ let stage_status = match result. stage_status {
132+ CoreThroughL2StageStatus :: Completed => "completed" ,
133+ CoreThroughL2StageStatus :: Escalated => "escalated" ,
134+ } ;
135+ ThroughL2Result {
136+ l1_decision : to_decision ( result. l1_decision ) ,
137+ l2_decision : result. l2_decision . map ( to_decision) ,
138+ effective_decision : to_decision ( result. effective_decision ) ,
139+ stage_status : stage_status. to_string ( ) ,
140+ escalation_cause : result. escalation_cause . map ( |cause| {
141+ match cause {
142+ a3s_sentry:: EscalationCause :: L1 => "l1" ,
143+ a3s_sentry:: EscalationCause :: L2 => "l2" ,
144+ a3s_sentry:: EscalationCause :: Sae => "sae" ,
145+ }
146+ . to_string ( )
147+ } ) ,
148+ }
149+ }
150+
86151fn to_decision ( d : a3s_sentry:: Decision ) -> Decision {
87152 let verdict = match d. verdict {
88153 Verdict :: Allow => "allow" ,
0 commit comments