@@ -31,7 +31,25 @@ pub struct Notice {
3131 pub published_at_ms : i64 ,
3232}
3333
34+ // EventDeclaration currently has no flow-targeting field. Keep the known
35+ // flow-targeted event contracts here so ingress, fan-out and body replay use
36+ // the same rule without inventing a declaration wire field.
37+ const FLOW_TARGETED_EVENTS : & [ ( & str , & str ) ] = & [ ( "prefrontal-core" , "digest_delivered" ) ] ;
38+
3439impl Notice {
40+ pub fn target_flow ( & self ) -> Result < Option < & str > > {
41+ let ( module, name, _) = self . identity ( ) ?;
42+ if !FLOW_TARGETED_EVENTS . contains ( & ( module, name) ) {
43+ return Ok ( None ) ;
44+ }
45+ self . headers
46+ . get ( "flow_id" )
47+ . map ( String :: as_str)
48+ . filter ( |flow| !flow. is_empty ( ) )
49+ . map ( Some )
50+ . ok_or_else ( || CoreError :: Invalid ( "flow-targeted event lacks flow_id header" . into ( ) ) )
51+ }
52+
3553 pub fn identity ( & self ) -> Result < ( & str , & str , u32 ) > {
3654 let parts: Vec < _ > = self . subject . split ( '.' ) . collect ( ) ;
3755 if parts. len ( ) != 6
@@ -77,7 +95,13 @@ pub struct Report {
7795 pub overflow : usize ,
7896}
7997
80- fn matches ( manifest : & str , notice : & Notice ) -> Result < bool > {
98+ fn matches ( manifest : & str , notice : & Notice , flow_id : & str ) -> Result < bool > {
99+ if notice
100+ . target_flow ( ) ?
101+ . is_some_and ( |target| target != flow_id)
102+ {
103+ return Ok ( false ) ;
104+ }
81105 let ( module, name, version) = notice. identity ( ) ?;
82106 let manifest = Manifest :: parse ( manifest) . map_err ( |e| CoreError :: Corrupt ( e. to_string ( ) ) ) ?;
83107 Ok ( manifest. trigger . events . as_ref ( ) . is_some_and ( |events| {
@@ -183,10 +207,10 @@ impl Runtime {
183207 . collect :: < Result < Vec < _ > > > ( ) ?;
184208 let mut report = Report :: default ( ) ;
185209 for notice in notices {
186- notice. identity ( ) ?;
210+ notice. target_flow ( ) ?;
187211 for ( flow, first_approved, approved) in & approved {
188212 if let Some ( approved) = approved
189- && matches ( & approved. manifest , notice) ?
213+ && matches ( & approved. manifest , notice, flow ) ?
190214 {
191215 // Upgrades retain the first approval point, so events
192216 // during an upgrade remain eligible for the current code.
@@ -222,7 +246,7 @@ impl Runtime {
222246 continue ;
223247 }
224248 let approved = install:: approved ( tx, & flow) ?;
225- if !approved. as_ref ( ) . map ( |a| matches ( & a. manifest , & notice) ) . transpose ( ) ?. unwrap_or ( false ) {
249+ if !approved. as_ref ( ) . map ( |a| matches ( & a. manifest , & notice, & flow ) ) . transpose ( ) ?. unwrap_or ( false ) {
226250 tx. execute ( "UPDATE event_receipts SET state='refused' WHERE rowid=?1" , [ rowid] ) ?;
227251 continue ;
228252 }
@@ -319,6 +343,18 @@ fn script_trigger(notice: &Notice, body: &[u8]) -> Result<JsonText> {
319343 . map_err ( |e| CoreError :: Invalid ( e. to_string ( ) ) )
320344}
321345
346+ fn body_targets_run ( notice : & Notice , body : & [ u8 ] , flow_id : & str ) -> bool {
347+ match notice. target_flow ( ) {
348+ Ok ( None ) => true ,
349+ Ok ( Some ( target) ) => {
350+ target == flow_id
351+ && serde_json:: from_slice :: < serde_json:: Value > ( body)
352+ . is_ok_and ( |body| body[ "flow_id" ] . as_str ( ) == Some ( flow_id) )
353+ }
354+ Err ( _) => false ,
355+ }
356+ }
357+
322358impl Runtime {
323359 pub ( crate ) fn event_preamble ( & self , run : & crate :: Run , manifest : & Manifest ) -> Result < Preamble > {
324360 use basal_host:: { CallClass , CallRequest , Dispatched , TransportError } ;
@@ -350,6 +386,9 @@ impl Runtime {
350386 . optional ( ) ?)
351387 } ) ?;
352388 if let Some ( ( Some ( body) , _, _) ) = & saved {
389+ if !body_targets_run ( & notice, body, & run. flow_id ) {
390+ return Ok ( mismatch ( "flow-targeted body does not name the run's flow" ) ) ;
391+ }
353392 return Ok ( Preamble :: Ready ( script_trigger ( & notice, body) ?) ) ;
354393 }
355394 if saved
@@ -424,6 +463,9 @@ impl Runtime {
424463 Ok ( body) => body,
425464 Err ( kind) => return Ok ( mismatch ( kind) ) ,
426465 } ;
466+ if !body_targets_run ( & notice, & body, & run. flow_id ) {
467+ return Ok ( mismatch ( "flow-targeted body does not name the run's flow" ) ) ;
468+ }
427469 let trigger = script_trigger ( & notice, & body) ?;
428470 self . store ( ) . write ( |tx| {
429471 tx. execute ( "UPDATE event_body_journal SET body=?2,digest=?3,retry_at=NULL WHERE run_id=?1" , params ! [ run. run_id, body, notice. digest] ) ?;
0 commit comments