@@ -241,11 +241,14 @@ fn author_stream(
241241 ) ) ;
242242 }
243243 let target = resolve_target ( & found. specs , selector, this_host) ?;
244+ let actor = actor
245+ . map ( |actor| resolve_target ( & found. specs , actor, this_host) . map ( |target| target. identity ) )
246+ . transpose ( ) ?;
244247 authorize_actor (
245248 & found. specs ,
246249 & target. identity ,
247250 this_host,
248- actor,
251+ actor. as_deref ( ) ,
249252 "stream-not-authorized" ,
250253 ) ?;
251254 let result = edit_stream_declaration (
@@ -631,6 +634,8 @@ fn edit_stream_declaration(
631634 catalog,
632635 path,
633636 & replacement,
637+ expected_identity,
638+ expected_host,
634639 expected_agent,
635640 name,
636641 launch,
@@ -744,6 +749,8 @@ fn verify_stream_candidate(
744749 catalog : & Path ,
745750 path : & Path ,
746751 candidate : & str ,
752+ expected_identity : & str ,
753+ expected_host : & str ,
747754 expected_agent : & str ,
748755 name : & str ,
749756 launch : Option < & StreamLaunch > ,
@@ -778,7 +785,9 @@ fn verify_stream_candidate(
778785 . map_err ( |error| AuthorError :: new ( "invalid-stream" , error. to_string ( ) ) ) ?;
779786 let spec = specs
780787 . iter ( )
781- . find ( |spec| spec. identity == expected_agent)
788+ . find ( |spec| {
789+ spec. identity == expected_agent && spec. bus_id ( expected_host) == expected_identity
790+ } )
782791 . ok_or_else ( || {
783792 AuthorError :: new (
784793 "unsafe-source-edit" ,
@@ -1891,6 +1900,45 @@ mod tests {
18911900 assert_eq ! ( fs:: read_to_string( path) . unwrap( ) , original) ;
18921901 }
18931902
1903+ #[ test]
1904+ fn stream_candidate_verification_matches_the_exact_host_agent ( ) {
1905+ let temporary = tempfile:: tempdir ( ) . unwrap ( ) ;
1906+ let root = temporary. path ( ) ;
1907+ let path = write (
1908+ root,
1909+ "agents.kdl" ,
1910+ "agent \" worker\" { host \" alpha\" ; command \" sleep 60\" ; stream \" existing\" {} }\n agent \" worker\" { host \" beta\" ; command \" sleep 60\" ; stream \" existing\" {} }\n " ,
1911+ ) ;
1912+
1913+ assert_eq ! (
1914+ add_stream(
1915+ root,
1916+ "beta.worker" ,
1917+ "beta" ,
1918+ Some ( "beta.worker" ) ,
1919+ "webhook" ,
1920+ None ,
1921+ )
1922+ . unwrap( )
1923+ . result,
1924+ AuthorOutcome :: Changed
1925+ ) ;
1926+ assert_eq ! (
1927+ remove_stream( root, "beta.worker" , "beta" , Some ( "beta.worker" ) , "existing" , )
1928+ . unwrap( )
1929+ . result,
1930+ AuthorOutcome :: Changed
1931+ ) ;
1932+
1933+ let authored = fs:: read_to_string ( path) . unwrap ( ) ;
1934+ let document = KdlDocument :: parse ( & authored) . unwrap ( ) ;
1935+ let agents = document. nodes ( ) ;
1936+ assert ! ( agents[ 0 ] . to_string( ) . contains( "stream \" existing\" " ) ) ;
1937+ assert ! ( !agents[ 0 ] . to_string( ) . contains( "stream \" webhook\" " ) ) ;
1938+ assert ! ( !agents[ 1 ] . to_string( ) . contains( "stream \" existing\" " ) ) ;
1939+ assert ! ( agents[ 1 ] . to_string( ) . contains( "stream \" webhook\" " ) ) ;
1940+ }
1941+
18941942 #[ test]
18951943 fn stream_authoring_enforces_authority_nix_ownership_and_canonical_validation ( ) {
18961944 let temporary = tempfile:: tempdir ( ) . unwrap ( ) ;
0 commit comments