There is no right answer to your question and a lot of it comes from personal taste and out of need.
Most engines often need the ability to deliver both asynchronous and synchronous events because certain situations are easier handled in one fashion than others. Secondly, in the case where you are using systems to update your entities in your example, you will need a means to dispatch events between systems specifying single entity for delivery, collection of entities, or all depending on the scenario.
Next, I generally prefer to keep scripting centralized in a script system. If other systems want to trigger some ScriptEvent, I prefer they raise an event and I have the script system capable of listening for that case and dispatching it to the appropriate script handler. This has the benefit that it allows other parts of the system to intercept and handle the same event and keeps the implementation of the script system abstracted so if I change internals or perhaps the language of choice, the engine is unaffected.
In the case of your collision example, when the system detects a collision has occured between two entities, simply send a CollisionEvent with source entity and the colliding entity as parameters. The scripting system sees this event and dispatches it to your script.
In my specific solution, my script system looks for a specific method definition called OnEvent and if found, it automatically hooks the script into the event system. It is worth noting that a single logic component is linked to an entity enabling scripting behavior. The script can require and include any composition of behavior needed.
This allows the script system to delegate default event dispatch into the scripts logic. Additionally, if the script wants to be notified of other events perhaps from other systems, I just call RegisterEvent(eventName) inside the script during it's loading and the script system links that script instance to that event name. When that event fires, the scripting system is responsible for taking the event parameters, packaging them into a lua method call and then calling OnEvent with the event name and the arguments.
In the corner cases perhaps where system A needs to call into the script system and invoke a particular behavior and you'd rather not involve the event system for it, you could always expose a method on the scripting system like RaiseEvent(EventObject) and allow the scripting system to again decompose the event into the scripting language's needs and dispatch the handler.
This is my own preference, but there is nothing that prevents scripting from being a fundamental part of the entire engine where each system can call into it. I've seen some component frameworks where any component can be ammended with a script to alter it's base behavior.
Either approach relies on a specific amount of abstraction to keep details from leaking into other parts of the engine so that refactoring and extending can be done easily without signfiicant time and costs.