Thanks for writing out the logic of how you would achieve that elsewhere. My brain is a bit foggy today and I'm not in a good spot to write out all of the logic, but let me tell you how I would get started.
This is a non-scripting approach, as I agree, the scripting side of Construct removes a lot of the advantages of Construct.
Create a Family. I'll call it inventoryFamily for now.
Think of the Family as the equivalent of a class. Of course a lot of differences though. Adding sprites to this family will add all the behaviors, effects, and instance variables to the sprite.
To start, will add a string instance variable to the inventoryFamily. I'd just call it inventory.
Create a function for addToInventory. One param will be uid, which first condition will be to select inventoryFamily by UID.
Now you are sure you have the instance you want. Another param can be the inventory item to add, whether it is a word or a reference number. Adding would set inventory = inventory & "," & param (if inventory has value). You can also use scripting in an event, if you want to make use of like .split(',') .push and .join(',').
If another action needs to be taken I'd make another function for that. Sometimes I'll make a function for each different action and other times I'll make one function and pass a param for the different actions. Of course the latter turns into a long event of elses, but if it is only a few actions for each sometimes I'd prefer that to keep them organized and not pollute the function namespace.
This also isn't my favorite, but I'd probably do call function by name. That way, the name of the follow-up action (removeMoney, modifyHealth) could be stored wherever you need it (with the item type; if action from the carrier, would create an instanceVariable on the inventoryFamily of pickupItemAction; or pass the param into the function).
It would be great to be able to attach a function to an object such as a method, but the functions that first grab the instance by UID achieve the same thing.
custom actions canʻt return a value
Does creating a function with a return value not take care of this?
As for some of the other limitations you mentioned - let me remind you my brain is foggy and also mention you prob know more than I do, so this isn't disputing what you say - but I'm not sure about some of the direct downsides or limitations. For example, in general you don't want to pollute the global space, but what advantage is it if all Construct objects were NOT in the global space?
I kind of suggested using scripting because it sounded like you wanted to adhere to common programming practices. I almost always use events, unless I want to hit an API or its code I want to use elsewhere.
I think the thing I like the most about using Construct (non-scripting) is the syntax check. It will prevent you on the spot from using a variable that isn't declared or missing a comma or period somewhere. It's not really even a pain point in my normal dev/day job, but its just nice that when you build your project to preview your game, you don't have everything breaking because of a typo.
I know that didn't address everything, but maybe it's all I got, ha!