WordPoints / hooks-api

A basic API for hooking into user actions https://github.com/WordPoints/wordpoints/issues/321
GNU General Public License v2.0
0 stars 0 forks source link

What are events? #116

Closed JDGrimes closed 8 years ago

JDGrimes commented 8 years ago

In https://github.com/WordPoints/hooks-api/issues/109#issuecomment-197928937 we noted:

Actually, I'm not so sure that we don't need to reconsider how we do this in the UI for the points hooks themselves, because the state of the events API continues to evolve. Related is #110, #111, and #95. Are events supposed to work in both directions now? It think we need to redefine the nature of events, but that is something for another ticket.

This is that other ticket. Its goal is to explore what events are, why they are, and whether they should change.

JDGrimes commented 8 years ago

Background

Events were added to the API in order to compose the actions. We didn't want to hook reactions directly to the actions, because that would lead to unnecessary duplication. It isn't as simple as an event encapsulating a single action. Instead, it might compose several different actions together, all of which trigger that particular event. The event was also a way to describe that set of action in the UI. I suppose that in large part, events are a very UI oriented feature (which any composer naturally is), rather than a necessary part of the API itself.

In addition to the above, it is true that we also wanted events to compose not only actions that triggered the event, but also actions that would undo or reverse it. This was needed because we wanted points hooks to automatically remove the awarded points when an action was reversed.

This could just as easily been accomplished by having multiple events, and hooking them both to the reaction. (Or multiple actions directly.) However, the different events/actions have to be composed at some point, and for the points hooks we didn't want to force the user to do this in the API. Rather, we wanted it to happen behind the scenes. And so once again the events were put into service mainly as UI oriented composers.

All of this does make us wonder if perhaps the events are too closely tied to just one single UI implementation, that envisioned for the points hooks. It should be kept in mind though that we also wanted the same thing for the badges, as well as for the ranks. So it wasn't something that was entirely points specific. In fact, one of the main reasons for designing the API this way was so that it would be reusable across these different components. And I suppose that it would be, as it stands. The question we might want to ask though, is whether it would be broad enough to take in other potential uses as well. Although broadening it too much could risk severely limiting its cross-component usefulness.

JDGrimes commented 8 years ago

Are events too implementation specific?

So we likely need to keep the events around in some form in order to continue to transparently compose the actions across these components. But if we want to make the hooks API as broad as possible, perhaps the events shouldn't be a part of the core API, but rather an extension of it. That would have a lot of implications though, and I'm not even sure that we want to make the API as broad as possible.

If we did want to make the API as broad as possible, one thing that we could do is reintroduce something sort of like firers, let's call them action handlers, that would move most of the action handling code out of the router. So then the event code would all be in an action handler (which we'd use across these different components). The router would basically just instantiate the action object and check if it should fire, then loop though the action handlers and pass the action object to each of them. So in the future we could do other things with the API if we wanted to, by creating another action handler that didn't bother with events and such.

The problem with that is that I'm not sure it would really be possible to reuse the other parts of the hooks API sans the events. They are pretty tightly coupled right now. And we could decouple them, but I don't know that we'd really stand to gain much by that. If our theoretical use-case is so different that it doesn't need events, does it really need another API entirely? I think that maybe in this area we're over thinking it right now, and we can decide to broaden the API later if we need to. It isn't something on the immediate horizon.

JDGrimes commented 8 years ago

Events, yes; action types, maybe

So it seems that right now we might want to keep the focus of the events API on how it needs to be used to compose actions so that these components can work properly. It is possible that even the way that the points hooks should operate may change in the future, but I think our API is sufficiently forward compatible to accommodate that when the time comes.

The main bit of strangeness in the API that I just can't get over is the fact that toggle events have multiple action types attached to them. So the event is triggered when it occurs but also when it is reversed. It is necessary for the reactions to be triggered in both cases, but I don't really see the term "event" as properly encompassing both of these. It's possible that we could find a better name, but I honestly don't think that we really need to worry about it. They just have the main action type which triggers the event, and another action type that reverses it. So the second one is kind of secondary.

Even though it is the inverse, events don't actually work both ways, because the toggle on is for a specific status, and toggle off is for any other status. So there are many ways to toggle off, but only one way to toggle on.

JDGrimes commented 8 years ago

Entity-relative event registry

One idea that I have been toying with though is registering the events relative to the entities, rather than having the action types this way. This would present a problem with the stateful events, but setting that aside for a moment it is an interesting idea in regard to the toggle events.

So we'd have an event like post publish, and another like post depublish. What we'd do then is somehow determine how different events related to each other based on how they relate to the entity. Possibly this would have to be hard-coded between the different events (like registering them with a reverses => event_name or opposite_of => event_name flag or something), or perhaps it would be determined at some level of the API based on what status the entity is being transitioned to and from.

Something sort of like this has been suggested before, I think possibly in https://github.com/WordPoints/hooks-api/issues/79#issuecomment-174194340.

One way that this could be done rather than hard-coding is having each event define exactly what entity attribute is modified by that event and how it is modified. Then perhaps reactions might want to listen for all events/actions affecting a particular attribute (or when that attribute was modified in a particular way) and not just a composed event. Though perhaps there would be times that we only want to listen to a specific subgroup of actions that cause that change to occur, and not all of them.

However, even if that would work for to replace toggle events, it doesn't really apply to the repeatable events. https://github.com/WordPoints/hooks-api/issues/111#issuecomment-194312324 is kind of related.

Really, events are meant to be about user actions, and toggle events are after all just one type. So designing the event API around them specifically would be a mistake. Perhaps they should be abstracted somewhat, but I don't think that the events API itself should go away, and likely the action types wouldn't be able to either.

JDGrimes commented 8 years ago

Events are forward compatible

As far as our forward compatibility concerns go, I guess that in a way the events themselves act as a flexible part of the API. If we want to use the API differently in the future, we can just create entirely new event types. Even if, for example, we wanted to change the way the points component works in the future, so that it would handle spam or other status changes in a different way, the API could accommodate that either through custom action types, or an added custom API. There really is a lot of flexibility here. It still does seem odd to be calling these event action types though, and maybe "fire types" would be a better term. Even then, it seems odd that the API functions that way, but a refactoring that takes all of the different event types into account is not immediately apparent.

JDGrimes commented 8 years ago

Forward compatible vs forward composible

What if instead of the events composing actions for multiple fire types, the set of actions for each fire type was a separate event, but these events were connected via the API somehow, like maybe with a flag on the event as noted above? The problem though is that the events really wouldn't be useful in and of themselves, since post depublish isn't really its own event, it is a combination of events that can be triggered by a variety of different actions. But then maybe we would just have several reversal events for post publish; one for post spam, one for post draft, etc. (We'd need one for each custom post status too, I guess.)

But just greater flexibility in the API doesn't entirely solve our problem, because when the time comes that we want to modify things we'd still have to do a lot of recomposing, and existing modules would each have to be recomposed too. So a more flexible API would be more forward compatible, but it wouldn't let us automatically recompose existing API elements. The only way to do that would be to pursue a more descriptive API as outlined above, where each event/action described exactly how it affected each entity attribute. Then we could change what sort of entity attribute changes we were interested in, without the actions having to be modified at all.

But even then, I don't think that we can fully anticipate what information we'd need about each arg. So we can provide some info, but we don't exactly know what information we're going to find important down the road. So it's still true that automatic recomposibility does break down at some point. But autocomposibility is an important API feature, so we want to be as forward composible as possible. Perfect forward composibility is likely impossible, at least with the nature of the WordPress hooks API. But since we know that we might want to recompose this in the future, it makes sense that we should try to pursue making this as forward-composible as possible.

JDGrimes commented 8 years ago

Descriptive event arg API

This sort of brings me back to the descriptive event arg API discussed above (https://github.com/WordPoints/hooks-api/issues/116#issuecomment-198099261).

The main issue with that idea was that it seemed too much aimed at specifically the toggle events. And that was true because of the way that we were approaching it, from the registry perspective, but it doesn't have to be linked to a per-entity event registry necessarily. Viewed aside from that, it seems to me that there is no reason that a descriptive event args API couldn't encompass any type of event—different event types would just naturally be describing different sorts of relationships with their args sometimes.

So assuming that we wanted to create such an API, where each arg was registered with some info about how specifically it related to the event, what info would we be able to offer? Two main things come to mind:

For example:

So for the "usage" events, we'd have info about how the entity was being used, and for "modification" events, we'd offer info about how it was modified. (Possibly some actions would do both of these at once, I don't know.)

Again, this should offer more flexibility, and hopefully more forward compatibility and composibility as well. The main question is just how much forward composibility it would offer. That would depend primarily upon two things:

  1. Completeness (number of actions related to each entity compared to total actions in WordPress that affect that entity), and
  2. Parity, for want of a better word (whether the info offered about each arg matches the info needed by the reactor/extensions/implementation as a whole).

Basically, to be optimally effective, we'd need to register as many different actions as possible, and be as descriptive as possible about each of their args. There will of course be difficulties in doing that, but before we consider those, let's understand exactly what would be at stake here, were we to rely on backward composition at some point.

A lack of 100% completeness, for example, would be compare to the current API as having an event registered without all of the actions that could trigger it. That could mean it only fires half as much as it should, or that it is treated like a repeatable event when actually it should toggle.

Exactly how bad it would be would really depend entirely on the particular use-case.

A lack of parity would likewise be bad, but it would generally mean that backward composibility would just not be possible at all. Its possible that in some use-cases partial backward composibiltiy would be possible, but usually it wouldn't.

That said, let's consider what might keep us from achieving a sufficient level of completeness and parity for backward composibility to be a real option in the future.

First, let's deal with completeness. Before we even begin talking about registering every single WordPress hook relating to an entity as an action, we should think about the fact that even that wouldn't guarantee 100% completeness. Some modifications to an entity might not be accompanied by actions at all, and in that case there is very little that we can do. This is especially likely to be a problem with plugins, but I wouldn't rule core out on it entirely either.

Then, even assuming that we could register all core actions affecting an entity, we still wouldn't be guaranteed completeness because of the potential that plugins might be modifying that entity in different ways we'd find important as well. Of course, that's where modules come in, but I think that this could potentially be affected by plugins that we wouldn't usually create modules for otherwise.

When it comes to parity, we also hit roadblocks before we even get started, because we won't always be able to tell from the action args what the prior state of the entity was before the action occurred, and that means that we wouldn't be able to really describe particular changes (at least for some attributes), just modifications generically.

Even in cases where we do know what entity attributes were modified, how would we know how to describe those changes? Perhaps we wouldn't actually have to, though. Perhaps what the meaning of those changes are would be best left up to each reactor/extension/etc. to decide.

But the simple fact is that we were entirely right when we said above that the nature of the core WordPress APIs don't allow us to achieve 100% forward composibility. In fact, they make it very difficult for us to implement a descriptive event arg API at all, because it doesn't tell us how the entity is being modified in many cases. But that doesn't mean that we can't get good enough parity and completeness to provide for forward composibility in the particular directions that we foresee ourselves possibly taking the API in the future. But I think we need to do a little research and get the cold hard facts on exactly what we'd need from the API for that to happen, and how well the core hooks API supplies that.

JDGrimes commented 8 years ago

Probable future directions of the API

  1. The main change that we might want to make in the future would be listening for specific kinds of entity status changes, and handling spam differently than when an item is just moved to the trash. This is something that has really been discussed to death, including in #66, #79.
  2. The only other possible change that I can think of, and which is slightly related, is that we might want to listen for all updates of an entity, and recheck if it now meets the constraints to have points awarded. See https://github.com/WordPoints/hooks-api/issues/66#issuecomment-167881161 for more details.

We need to consider for each of these:

JDGrimes commented 8 years ago

Spam differentiation

Handling a toggle off differently if the entity is being marked as spam is something that we had originally thought to do with the API. However, we gave it up for now because it was changing the way that points hooks worked, which wasn't the goal of this new API at all.

Feature description

To understand exactly what we might need from the action API in order to implement this feature, we need to first understand a little more about exactly what it would be doing.

The basic idea of the feature is that it should detect when particular events on the API are spam. In that case, it may want to take a particular action relative to the response to the original event that was prompted by the reaction.

Note that this is not specifically about entities being spam, but rather that an event itself was a "spammy" action on the part of the user (or whatever triggered it). Determining whether a particular event fire is spam, however, is something that isn't WordPoints's job, and so it must for that rely on information about the event provided to it by core and plugin APIs. The primary info provided by APIs that might indicate that an event is spam, is when a particular entity is marked as spam. Note, however, that it an entity being marked as spam doesn't necessarily mean that events relating to that entity were also spam; and conversely, an event might be spam even though none of the entities related to it were marked as spam themselves. It's about the events, not the entities.

Still, it is generally only entities themselves that have spam statuses, or spam/ham APIs associated with them. WordPress's action API doesn't have a spam/ham API built in of course, which means that events themselves are seldom marked as spam directly.

As such, we have to deduce whether events are spam based on what info we are supplied about their related entities. This means that, generally speaking, we can only determine whether specific kinds of event are spam:

And even this is only possible when the related entities have associated spam/ham APIs. (And even then we are making an assumption. We don't know with certainty that the state of the entity when it was created/published is what caused it to be marked as spam; it is possible that it was because of intervening edits, for example. But we'd have to determine when the time came whether these assumptions are sufficiently reasonable to make, generally.)

For example, we could consider a comment leave event spam if the comment was later marked as spam. For a post this wouldn't be possible though, because posts don't have a spam status in core.

And we could consider any events performed by a user that is later marked as spam spam, but only on multisite where users can be marked as spam.

Needs from the action API

So this feature is about events that are spam, not entities specifically, it is only that we don't usually have any other indicators to go by that would tell us that an event was spam. This means that this feature is really broader than just action args themselves, although that may be the main (and, yea, perhaps only) resource that it has to draw on.

Even when we limit ourselves to thinking mainly of the action args/entity APIs, this is more complex than each entity just specifying a status attribute. The "status attribute" is not exactly a standard thing across all entities, and and how an entity's status is determined actually depends upon the type of entity. Some, as we have said before, don't really have statuses at all, and even those that do don't necessarily have a spam status—and even if they did, the name of the "spam" status might not be entirely consistent across the entities either. But more importantly, some entities do have statuses, but specify them differently, like having an is_spam attribute which is a boolean value that determines whether the entity is spam.

So determining an entity's status is something that has to be done on a per-entity type basis, and as such would likely be better as a part of the entity API itself, rather than the action args API. (I'm not sure exactly how we'd deal with the case where spamability was added to an entity by a plugin rather than core, but that's not our concern at the moment. Probably a filter in the entity status method would work nicely.)

That wouldn't give us forward recomposibility exactly, mind you, but it would, we hope, provide some of the feature parity needed.

But not all. We wouldn't need the actions and events themselves to check whether the entity was spam then, that could really happen at any level of the API (but I suppose would likely be in extensions or reactor). But in order to know not just whether it was spam, but whether it had just become spam, we would need the actions to provide an API for determining what the entity was like prior to when the action occurred. (Otherwise, we'd end up risking repeats of whatever response we take when the entity is marked as spam.)

Actually, this can partly be provided for simply by only registering actions for an event that will only fire when the entity status is changing. For example, the comment leave and comment deapprove actions are hooked to the transition_comment_status event, which by very nature only occurs when a comment's status is being transitioned. For such an action, if the entity is spam after the action occurs, this alone implies that it just became spam.

However, I doubt whether we will find such actions ubiquitous, in which case we would have to provide some API for comparing the before and after shots of entities, so that we could check whether the before and after were both spam. Actually, the only piece of info that we'd need from before would be whether it was spam, the rest wouldn't matter for this particular feature.

One means of doing this, without actually modifying the current API, would be to just register the spam actions for each event with their own action type as we go along, even though we'd handle them the same as a generic toggle off right now. The action objects would check internally whether the entity was becoming spam, so an external API wouldn't be needed. Of course, that wouldn't actually increase forward recomposibility, though we'd be hoping that it would give us feature parity—and if it didn't, well, we'd be little better off for it.

And of course, this is assuming (and this would be a necessity for any sort of API), that we'd actually be able to determine whether the entity was spam or not prior to the action. And I doubt that that would always be possible, though we might investigate it for each entity that we currently use. In any case, we must recognize that the WordPress hooks API cannot be relied upon to always make this possible, just by its very nature.

Now that we know what the API requires and whether the WordPress hooks API will make it possible, we should consider how important backward recomposibility will be. But first there is one more requirement that we'd need to consider in terms of completeness:

We'd likely want to listen for the entity becoming spam at any time in its future after the event occurred, not just immediately upon depublish/toggle off. Which means we'd need to listen to all entity status transitions, not just publish/depublish. And that would likely include listening for unspamming too, to undo whatever we did when the entity became spam. Note that we didn't do that previously, the original event had to fire again for that to happen. The spam response of the reactor wouldn't be reversed when the entity was just changed to a different non-published state.

WordPress hooks API parity

These things now understood, let us consider whether the WordPress hooks API allows us to meet these requirements for each of the entities that we currently support:

Entity Completeness Parity
post1 yes (single action) yes (value supplied)
comment yes (single action) yes (enforced)
term N/A N/A
user2 yes (ham/spam actions) no (not enforced)
user_role N/A N/A
site yes (ham/spam actions) no (not enforced)

1. Posts do not have a spam status in core.

  1. Users can be marked as spam only on multisite.

Completeness above indicates whether or not it would be possible for us to listen for every possible change to the entity that could affect its status as spam.

Parity indicates whether or not it would be possible for us to determine whether the entity had just become spam (or ham) during that event.

(We're assuming of course that the entities won't be directly modified from outside of WordPress or using $wpdb directly, etc., but only with the functions that WordPress core provides.)

As you can see, both of these are fulfilled for the post and comment entities, although of course posts don't actually have a spam status (not by default anyway). The transition_post_status hook fires every time that a post is updated whether the status actually changes or not. The transition_comment_status hook doesn't fire unless the status actually changes. In both cases, the previous status is supplied, so we can tell whether it changed or not. Another noted quirk is that the comment action isn't fired when a comment is first created, although that isn't necessarily important for our current discussion.

The more important issues arise with the user and site entities, which both provide actions for when the status is transitioned, but do not in fact enforce that these actions fire only when the status has actually changed. Neither provides the previous value either, and there is no way for us to capture, even with the most ingenious of hacks—it just isn't possible. Take a look a update_user_status() and update_blog_status() for yourself. But because nothing is allowed to be simple, the blog status can also be modified be update_blog_details(), and it does check if the status has changed before calling the ham/spam actions. And actually, I believe that the update_*_status() functions are just assumed to be only called when the status is changing. It is implied, but again, not enforced.

So we could conclude here that the WordPress hooks API meets the requirements reasonably well, although by coincidence and not by nature. And we can easily foresee the potential for it falling far short, especially in the case of some plugins.

Importance of backward recomopsibility

But let us consider how important backward recomposibility would really be if this feature was to be implemented. How important is it that this feature work seamlessly for every entity?

Of course, this can very much depend on exactly how each reactor taps into this feature. For example, if the points reactor just handles reversals slightly differently, then backwards recomposibility wouldn't be important at all. But if it is handling only spam and ignoring other reversals entirely, than it might make a big difference. Let's assume the later, just in case we do decide to do that.

Now, what would be the issues that we might run into? Well, there would be two main types: those arising from a lack of completeness, or those cause by a lack of parity.

At first it may seem that the former would likely be the bigger problem, since many plugins might be action deficient. However, when a plugin doesn't have very many actions, that will probably often mean that we wouldn't have the actions for the event to start with.

Anyway, the cause of a lack of actions would be that we wouldn't be able to listen for all spam/ham operations. Missing a spam operation would mean that points wouldn't be removed, which might not be what we'd consider a huge problem, although of course it isn't ideal. The result is that the spammer will end up with more points than they should have, although the practical effects of this will likely be minimized by the fact that spammers will likely get removed from the site eventually anyway. That wouldn't cause points to be subtracted from other users who were benefited by the spammer's actions, however, such as when a post author is awarded by spam comments. Although that too might be mitigated by reversing such awards if the user itself is marked as a spammer, but this possibility is limited to multisite by default.

The converse is also true in regard to despamming actions. A lack of these would mean that users wouldn't have their points restored when an entity was despammed, although all in all that is likely to be an uncommon thing, I think.

But as noted above, the more likely scenario may end up being a lack of parity, when we can't determine the prior status of an entity at the time an action fires. The result of this would be that we'd end up either skipping out on registering the action (which would result in behavior like that above), or that we'd be risking that the action might fire multiple times after the status had actually changed. In fact, I suppose that it would be possible for the action to fire without the status having changed from its original state at all, which might be the more problematic possibility. (It might seem counter-intuitive that an entity would ever have the spam status upon its initial creation, since nobody intentionally wants to create something marked as spam, after all. However, I think there may be times, as is perhaps the case with comments, where a spam check happens before the comment is even inserted into the database, and so it may end up being flagged as spam at the time that it is created.)

But although this may be a more common issue, it may also be less serious than the other, if the actions that reactors take in regard to spam are generally idempotent. That's an assumption, granted, but it might indeed be a worthy calling. Note again however that this would affect despamming as well, which perhaps has less chance of being idempotent, and in that case would cause points to be awarded multiple times.

Of course, this could be mitigated at the action level by keeping track of entity status transitions in a custom database table. Not ideal, to be sure, but it is a solution that could be shared across action objects, and perhaps it would be worth it to introduce such a feature, if we felt it necessary.

One noteworthy strategy that I happened to think of for mitigating the ill affects of imperfect backward recomposibility upon the introduction of such a feature, would be to continue to perform regular reversals for entities that didn't specify whether they had any spam status or not. We could perform only spam reversals for entities that specified spam actions, and we of course wouldn't run the spam reversals for entities that don't have a spam status at all. But for entities that we weren't sure about, we'd continue to perform regular reversals, not just relying only upon the entity statuses. In other words, we'd choose to run regular reversals for events that had no spam actions registered by related to an entity which could have the spam status. (This would assume that the entity status API would allow us to get a list of status types supported by each entity.)

JDGrimes commented 8 years ago

Entity change tracking

From https://github.com/WordPoints/hooks-api/issues/66#issuecomment-167881161:

So when a post is modified, how should this be handled by a cumulative reactor? This should be consistent regardless of whether the event is triggered by the change or not. But how can we be alerted to every change to every attribute of an entity and every one of its related entities? It isn't entirely possible to do that because its always possible that they will be modified directly without using the methods provided by WordPress. But that is and edge-case which is far beyond our control. Barring that, what we could do is have a set of actions registered for each entity child that are the actions upon which that relationship/attribute might be modified.

The other option, of course, would be to ignore future fires after a miss occurred. Although that would be consistent, I'm really not sure that it is a good idea. It makes sense that if a post is published without a particular tag and then that tag is later added, the user could be awarded again by the hook being refired.

That last sentence basically sums up this feature's purpose: we might want to award points not just when a post is published and has a particular tag, but also if a published post is given that tag. Stated more generally, we might want to award points based on whether an entity matches a particular state, no mater what action triggers it coming into that state, and not just when one particular attribute is changed.

Since we are using the post entity as an example, consider the post publish event. We wait for a post to be published, and then award points. But it isn't as simple as that, because we also limit whether or not the points are awarded based on certain constraints, specifically the conditions. So we might only award points when a post is published and its attributes and/or relationships also match particular conditions. If those conditions are met first, and then the post is published, we award points. But if those events happen in the opposite order, we don't. This feature's aim would be to stop that disparity by awarding points regardless of what order the conditions were met in.

(And note that this can already happen in the other order, if we unpublish and republish the post. So the idea was basically that each reactor/points type should respond the consistently in both of those situations. Either it should ignore later fires if we missed, or hit at a later date when any of those things changed.)

In other words, it is a difference between cumulativeness and statefulness. It isn't the act of clicking the publish button that we are awarding points for in this case. It is the state of having a published post to your name, that matches certain conditions. Of course, that is only for the post author. For the comments on that post, the case becomes less clear-cut, because we might be wanting to award the user for commenting on a post that was already in that state (in which case we wouldn't want this feature), or we might be wanting to the award the commentor just for leaving the comment, and not specifically for being in the state of having left the comment. I suppose that the same could really be true for the case of the post author as well, we might want the author to be awarded only if the post had already been marked a certain way by an admin or something...

So it seems that this really isn't a universally valid assumption. We'd have to detect whether we wanted this feature to apply in each individual case. Perhaps that would be done through a setting on the reaction, but it would be better not to force the user to make this decision if we could possibly detect his ourselves.

(Note that this relates to retroactivity, since an event that should be treated cumulatively and not statefully probably shouldn't be able to work retroactively either, although we may still ultimately leave that up to the user. But still, being able to tell whether an event was cumulative could have application beyond just this single feature.)

So how would we detect whether a reaction should use this feature? This is something that has to happen at the reaction level I think, because based on the above discussion it seems that it might depend upon the target. Although the target alone didn't seem to conclusively decide the issue in either direction.

We don't really want to force the user to decide this, because it seems so convoluted, but on the other hand, I can't seem to wrap my mind around how we'd ever determine this from the reaction settigns—in fact, I'd say it's probably not possible. Perhaps a solution to this would be to let the user decide, but not by checking a box in the points hooks UI, but by having an entirely different UI for the stateful "hooks". We'd then either use a different reactor for each one, or set a hidden flag that would tell the reactor how each reaction should be handled in this regard.

So in other words, maybe we should really split points for actions vs possessions, and put each in its own UI. Then each entity would be given a particular value, so that for example a post might be worth 5 points, and a post whose attributes and relationships matched certain conditions would have a different value. Perhaps these would even be additive, so that each different tag would add so many points to the basic amount per post, for example. #3 is kind of related to this, and in fact I've been toying with the concept of having conditions have points values associated with them before (so that each reaction would have a base points amount, which the conditions would add to only when met), though it seems I've neglected to create a ticket for that.

But again, what we're proposing here is that this would be separate from the actions, like visiting a site. (I guess really it'd be sort of like using a different API for the toggle actions vs the repeatable actions, although not exactly.)

So then, why would we need to use the hooks API we're building at all for this? Couldn't we use another API entirely, like maybe the retroactive/query API? No, because to use the query API exclusively we'd have to be constantly running innumerable expensive queries. Yes, we'd use the query API too, and possibly even make this retroactive by default, but to know when something may have changed and we should rerun the queries, we need to utilize the hooks API. Actually, I guess we'd probably not even need to run the query then, either. The only reason that we'd need to is if we wanted to only award points when the actual number of matches increased above the previous high, rather than just whenever a new item was added. So in other words it depends on how we want to handle reversals and such. (It might seem like we'd always want to reverse in this case, although it actually isn't so clear-cut. We still have the question as to whether you should still get points just because you possessed that post at one time, and not just only as long as you do possess it.)

It might also at first seem that this would overlap with the badges API (since we'll likely want to award so many points with each badge—multiple reactors per reaction, anyone?) so much that we might suggest just waiting until we introduce badges to worry about his. Then when that time came we could deprecate the related points hooks. But this is actually different than badges, in that badges would be about a specific threshold, whereas this would be about the value of each item individually.

This actually seems like a pretty reasonable idea, and as I said above, I think that this is the only way that we'll possibly be able to differentiate between cumulative and state-based reactions. So let's assume that we'll be doing something of this sort if we implement this feature in the future.

So basically what we'd have would be a list of entities, and their children. And for each of those entities and children the user would be able to create reactions that would award points when that user possessed a matching entity, or an entity with a matching child.

As far as the fact that we currently allow different targets for an event, what would happen for awarding points to a comment author based on a particular post attribute would be that the user would have to posses a comment on a matching post. Even as far as awarding points to a site admin for posts on their site, they would be awarded for every matching post on a site that they posses. (This would be because the reaction would then be on the post entity, not a post condition on the site entity, which would of course award only once per site.)

It does seem as though we may find that we want to split this into a similar but slightly different API in some way, but for now let's consider what we'd need from the action API to make it work.

JDGrimes commented 8 years ago

Cont.

Needs from the actions API

In order for this feature to work, we'd need to be notified about every change to every entity. Exactly how we would decide to which changes each particular reaction needed to listen is another matter, but for the feature to work at all, being able to know about every change is a prerequisite.

Our old friend the descriptive hook arg API might seem to be a means of actually deciding what actions each reaction should listen to, but we should note that many of the WordPress actions fire only when an entity is modified in some way, not for each specific attribute. So we would either have to register separate actions for every attribute of an entity, or else decide whether we needed to listen to each particular fire of that action at run time. So perhaps our descriptive API wouldn't need to describe the actions themselves, but rather describe the action fire when it occurs, and then from that we would decide which reactions should be hit.

In other words, we'd likely define events (as far as the reactions go) differently than we do now, in terms of particular entity changes, not in terms of actions. Though of course the actions would need to alert us to those changes, but deciding which reactions should respond wouldn't be as simple as querying them by event slug. Instead we'd have to do something like list each change that action fire had performed, and then query for all reactions that need to listen for each change.

But either way, at some point we're going to need to know what an action fire affects on a per-entity and per-entity child basis. And this will need to encompass every single entity and and entity child.

JDGrimes commented 8 years ago

Cont.

Do the WordPress actions make this possible?

What we need then in terms of completeness is for there to be actions we can hook to for every single change to every single entity or entity child. A tall order.

And in terms of parity, we need each of those actions to tell us exactly how the entity/entity children were changed.

Below I outline every time that each of the entities that we currently register is modified in WordPress core. This includes creation or deletion of the entity as a whole, as well as modification of any of its attributes. It does not include creation of relationships, so it is incomplete in that regard.

The "has action?" column is obviously a measure of completeness, whereas the "can get changelist?" column indicates whether the available action(s) (if any), provide the needed parity.

Post
function modifies has action? can get changelist?
wp_media_attach_action() post_parent no no
wp_delete_user() post_author yes (delete_user) yes (via query)
wp_xmlrpc_server ::attach_uploads() post_parent no no
do_trackbacks() to_ping no no
trackback() pinged, to_ping no no
wp_update_comment_count_now () comment_count yes (wp_update_comment_count) yes (action args)
remove_user_from_blog() post_author yes (remove_user_from_blog) no
add_ping() pinged yes (add_ping filter) yes (via query)
set_post_type() post_type no no
wp_delete_attachment() all yes (delete_post) yes (via query)
wp_delete_post() post_parent yes (before_delete_post) yes (via query)
wp_delete_post() all yes (before_delete_post) yes (via query)
wp_insert_post() all yes (pre_post_update) yes (via query)
wp_publish_post() post_status yes (transition_post_status) yes (action args)

Support is reasonably good.

Comment
function modifies has action? can get changelist?
wp_delete_comment() comment_parent yes (delete_comment) yes (via query)
wp_delete_comment() all yes (delete_comment) yes (via query)
wp_insert_comment() all yes (wp_insert_comment yes (action args)
wp_set_comment_status() comment_approved yes (transition_comment_status) yes (action args)
wp_update_comment() all yes (edit_comment) no
wp_trash_post_comments() comment_status yes (trashed_post_comments) yes (action args)
wp_untrash_post_comments() comment_status yes (untrash_post_comments) yes (via query)

The inability to get a changelist in wp_update_comment() is serious.

User
function modifies has action? can get changelist?
update_user_status() any no no
wpmu_delete_user() all yes (wpmu_delete_user) yes (via query)
wp_delete_user() all yes (delete_user) yes (via query)
wp_new_user_notification() user_activation_key yes (wp_new_user_notification) yes (via query)
wp_set_password() user_pass, user_activation_key no no
get_password_reset_key() user_activation_key yes (wp_new_user_notification) yes (via query)
wp_insert_user() all yes (profile_update) no yes (action args)

The inability to get a changelist in wp_insert_user() is serious, though I'm unsure how important this actually would be. It turns out that I was wrong about that, we can use profile_update.

Site
function modifies has action? can get changelist?
wpmu_delete_blog() all yes (delete_blog) yes (via query)
update_blog_details() all yes (specific to some atts) yes (via implication)
update_blog_status() any yes (specific to some atts) no (but assumed)
insert_blog() site_id, domain, path, registered no no

There is really no support here, but happily it likely wouldn't be of much importance anyway.

Conclusion

The fact that we're basically 1 2 for 4 in core means that we'll probably find parity even worse in plugins. We'd have to count on almost never knowing exactly what parts of an entity have changed. And even this does not take into account changes in entity relationships, which I'm guessing might have even worse support.

The only way to really get around this and achieve near-perfect parity would be to invent a very low-level API, that listened for all changes via the database query filter, parse the SQL for the queries, and determined from that what was updated. But of course, that's nuts. (If only there were actions in the $wpdb update(), insert(), and delete() methods, it would be slightly more reasonable, because then perhaps we could get the query args before the SQL string was constructed. But that's by the by.)

So we must count on almost never knowing what parts of an entity have changed when an action fires, unless we do something crazy like save a copy of each revision of an entity somewhere, which again is absurd (and wouldn't scale anyway).

But how important would parity be?

JDGrimes commented 8 years ago

Cont.

How important would backward-composibility be?

We can say with certainty that even if we register actions in a way that they'd be able to be recomposed in for this feature in the future, we won't be able to have complete parity for all entities and entity children. So it isn't so much an issue of recompomsibility as it is an issue of not having the actions available in WordPress at all.

In other words, we can likely have parity for some entities/entity children, but we won't have completeness—many entity children or whole entities would have to be left out.

So what would that mean for this feature, in practical terms?

Not being able to apply this feature to a particular entity/entity child is no different than just not having all entities/entity children registered, as is the case now. It wouldn't affect the operation of the feature for other entities/children, we just wouldn't be able to provide this feature for them specifically.

So there's no reason why we couldn't still introduce this feature, regardless of the level of completeness. We could support those entities that we had update/create/delete actions for, and we could support those entity children that we have parity for as well.

Practically speaking, we really have parity for the most important entities/children above. That is, most of the stuff we don't have parity for isn't so important. Who is going to want to award points based on most user attributes anyway? But for post attributes, yes, and so happily we do have parity for that. The only really conspicuous absence is for the comment attributes, due to the lack of parity in wp_update_comment() (though of course we could try to get that rectified in core).

For those entities that we didn't have parity for, I suppose we might just offer traditional hooks for them, although of course that wouldn't be ideal.

JDGrimes commented 8 years ago

Cont.

What would count as a possession?

There is another aspect of this feature that needs to be considered though, and that is that every entity of a particular type may not count as a possession. For example, we'd probably only want to count posts that the user has actually published, not just any draft or trashed post that the user happens to have. So an entity wouldn't just have to exist to count, it would have to have a particular status.

Perhaps that's where we'd need something similar to what events do now.

That is, unless this was just a built-in implication, that the entities, if they supported statuses, had to have a particular status. (Note that some entities inherit statuses via relationships, like a comment on a trashed post, so I guess we'd also have to decide what to do about that.) Though I'm not sure how we'd choose which statuses count? Would it be a whitelist, like only publish-like statuses, or blacklist, like only excluding spam- and trash-like statuses? However we did it, perhaps it could just be some basic cross-entity rules that we applied behind the scenes (and perhaps we could even allow that to be filtered per-entity at fire time or something).

Either way, we'd need to know for such entities not only when they were created and deleted, but also each time that their status changed. This means that we wouldn't be able to support the comment entity, for example, even though we have create and delete actions, because we don't have the update parity (although in the case of comments, we really can support them if status is all we care about because we have the transition_comment_status action that we can hook to, see above about the spam/status API).

But I guess a big question is whether status is all that we care about or not? The answer may also dictate whether we need some sort of 'possession definitions' to replace the events, although even if we only take statuses into account we may still decide that we need something like that anyway.

JDGrimes commented 8 years ago

Cont.

Related to the status or not, these are very similar to the conditions provided by the conditions extension. The only difference being that these would be set only under the hood by the developer, and not by the user. Which really leads us to ask why exactly these need to be set under the hood at all, why can't they just be regular conditions?

Actually, what we have been envisioning was basically that each condition would be in a different reaction. So one reaction would award 5 points for a post, another would award 2 more if the post had a particular tag, and an additional one 1 point if the post had another tag.

However, I see no reason why we couldn't let the user combine conditions, so that the extra 3 points would only be awarded if a post had tag a and tag b both. And likewise, the user could choose to award points for a post only if it met certain conditions. Like we said above though, setting conditions on the base reaction for the post entity would exclude any non-matching posts from ever being awarded points, even if they matched any of the other reactions for children of the post entity.

This is a little bit confusing, but I guess that it is up to the UI to ensure that users understand exactly what is going on. Maybe we'd sort of embed the reactions for the children within the reaction for the main entity, and likewise for the reactions for the grandchildren within the children, so that it would be clear to the user when the reactions cascade and when they are separate branches of logic. The result would be sort of like the proposed and/or API for conditions (#3), so that the conditions could be combined to work together as a group, or independently.

So we would still have just one visible condition per reaction, perhaps, but the other conditions necessary to produce the and/or affect would be included in each reaction, behind the scenes. I say that we'd add the other conditions behind the scenes, because otherwise there wouldn't be either AND or OR operation between the reactions, they'd end up all awarding simultaneously. We do indeed want that behavior for some of them, that is, we want some of them to be independent one of another. But in order for the cascading to work, we need the conditions of a "parent" reaction cascade down into the "child" reactions as well. I guess actually this wouldn't exactly provide OR conditions at all, but rather independent conditions.

Anyway, the point is that we could indeed allow multiple conditions to work together to produce an AND affect, and this could even happen on the main entity, so that the user could decide what conditions an entity must meet before a user would be awarded points for it at all, that is, before it would be counted as a "possession".

So then, why should we set certain conditions "behind the scenes" at all? What makes those conditions, or the status conditions, particularly special? I suppose it is only that we assume that is the way that the majority of users would likely always want things configured for that entity. But perhaps a better solution would to simply make these defaults that were automatically set for the user, but which were displayed transparently and indeed could be modified if they so chose.

JDGrimes commented 8 years ago

Cont.

A Square Peg?

On the other hand, the basic premise that we'd want to make this work that way, with a bunch of different reactions for each entity, might not be valid. It really doesn't seem very efficient to have to have all of these duplicate conditions behind the scenes. It would make more sense to just have a single reaction per entity. Or perhaps what we really need is a different API entirely. Actions would ultimately need to be involved in some way, but we need to handle them fundamentally different than the hooks API otherwise would. I can see some similarities to what we want from this feature and from the hooks, especially in terms of UI, but also for the underlying logic. I just get the feeling that we'd likely build the code for it entirely differently though if the hooks API wasn't already in place. Not necessarily a bad thing I guess, but it makes you wonder if we're really trying to make a square peg fit a round hole.

I guess to better understand whether a different API would be necessary, we should explore a bit more what we might want to do with this feature.

Say, for example, that we wanted to award less points for password protected posts than regular posts. This seems reasonable, but it means that we'd have to use a negative number of points for the reaction with that condition. We couldn't put the condition on the main reaction for the post entity, because then it would have to cascade down into all of the other reactions that they only affect protected posts. The only way to accomplish it other than setting the condition with negative points would be if this reaction was an alternate base reaction, entirely independent of the other man reaction for the post entity. However, in that case, the because it was independent, password protected posts would be awarded twice: once by it, and once by the other main post entity reaction. Of course, that could be prevented by setting a "post not password protected" condition (if available) on the other "main" reaction(s).

But as we said, allowing multiple "main" reactions for an entity like that would mean that the user would have to take care that they didn't end up configuring things so that they awarded points multiple times for a single entity. The only way that we could permit such a "solution" would be if we had an explicit OR condition API in place, so that the "main" reactions actually didn't have independent conditions, but cascading OR conditions. I guess that's really a feature that we've though we'd introduce in the future anyway.

But let's examine further the possibility that we just put a negative number of points in for the password protected posts instead. This would mean that instead of multiple points transactions awarding points (one for the post, another because the post had tag A, etc.), we'd have one transaction awarding points, and another transaction removing them. This seems strange.

A solution would be to make it so that the reactions didn't actually end up producing separate transactions, but instead somehow they would be aggregated into a single transaction in the end. I'm not sure how that would work though, and it has its own problems, like how in the world we would come up with log text then.

But then is it really even a problem that we'd award some points and then remove them in consecutive transactions? Or is it just that we perceive it as an issue? I suppose that really we could just think of it as it costing so many points to password protect a post. So when a number is negative instead of positive, that is the cost of possessing that thing, rather than the reward. It does seem strange at first, but now that I think about it more, maybe it actually makes a lot of sense. After all the points would be subtracted if the user decided to password protect a published post.

But though it makes some sense to us now, I'm not sure that it will always seem right to the user. It's a psychological thing maybe, but UIs should be as intuitive as possible. So what about times when the user isn't thinking of it as charging for something, but just awarding less? Basically the user goofed when they set the baseline number of points, and should have made it lower. But then how would they fix that? Would they have to update every other points value in the cascade to reflect the new lower baseline? I guess actually that that really depends upon their intentions. And I guess it is a decision that has to be mad on a per-sub reaction basis whether it is intended to make the user receive so many points total for an action, or just add a certain number of points to whatever the total ends up being. But then this feature wasn't exactly intended to be about totals—it is about the values assigned to individual entities and entity properties (defined by conditions on the entity children).

But let's entertain for a moment the possibility that we might actually be too strict in our intentions for this feature, and that we might be able to expand it to encompass that possibility as well. Maybe we could let the user choose how they wanted each sub-reaction's points value to relate to the base reaction's points value: +, -, =, *, /, etc. But how would we do that if each of these were entirely separate reactions? It would mean that we would have to invent some means of aggregating the results from all of the triggered reactions and then deciding what points to award. But even given that, we'd still have problems, like the potential for conflicts when multiple different reactions try to set the transaction at different points values. And it would also complicate reversals immensely when calculating how many points need to be removed/added when an entity looses that property. An OR condition API seems like a much better solution there.

However, that would only account for the = (set the total) relation, and we hinted at others above, like * and /, that might also be useful (especially the former). But maybe we could still allow for some multiplication conditions to be added, either by processing them before the other reactions at fire time and letting them affect the others that way, or, even better, by making them not be reactions at all, but rather cascading settings. Although I'm not sure that that second idea would work, because they'd need to cascade up the chain as well as down it. But I guess we could add the multiplication condition across the entire affected cascade, although that is yet another piece of extreme duplication, which makes us question whether we might be better off with another API entirely.

But then, there are other issues with the whole concept of the multiplication/division idea itself, like how the logs would be handled. Would we just multiply the number of points in each transaction? Or would we multiply the number of transactions? But I guess that wouldn't make sense if we were multiplying by 10 or something. But then would we include a note in the logs, or just be silent about the multiplication? Or would we award those points as a bonus on top of the other normal transactions, and include a note there? Or maybe the extra for each transaction that was multiplied we'd award a separate bonus (so if we were multiplying by 10 we wouldn't have 10 transactions, just 2, one with the normal number of points, and one with a 9x bonus).

Anyway, I guess we could make the feature work one way or another, but I think all of this just helps to demonstrate that maybe the hooks API, which would require us to have a bunch of different reactions with duplicated settings, isn't what we want here. (I just happened to think, for example, of the complexity of trying to craft the logic that would decide how to display each reaction in the UI. I guess we'd have to explicitly tie them together somehow, because otherwise this would take a lot of work, both to write the code for that and then just to execute it.)

JDGrimes commented 8 years ago

Cont.

A shared meta-API

So maybe we really should consider an alternative API. The trouble is that it seems that so many parts of the the current API would be useful. For example, the entities (although they aren't a part of the hooks API itself, actually), the conditions, the actions (but maybe a bit different than in the hooks API), the settings and the UI and response would also be similar (though that's partly just because we're talking about points), and also, the very nature of the API should also be similar: it should be composible, and we'll likely want to be able to use it across different "reactors".

So there is a lot of potential commonality here, we just need to sort out how much of that should be similar code vs shared code. Obviously, sharing code is great, but perhaps what we'll really be talking about in the end wouldn't be so much a shared API but some common shared low-level stuff utilized by both APIs.

To take things point by point from the above comment, we already have the entity API as its own thing, so we're good there. We should probably do something similar for the conditions API though, and in fact we've sort of already dabbled in that, though perhaps at a lower level, with the specs API (although we haven't been planning to do much with it yet, it was mostly just a mock-up). (Also, we'd likely need to update the conditions API for this feature anyway, so that we could generate the points logs, unless we want to still let the user supply the logs.)

As far as the actions go, we would need to utilize the WordPress action API as our base, and what we do would likely be similar to what we currently do in the hooks API, in that we need to tie those actions into the entity API. Only, we'd need the event args but likely without the events themselves. We've talked before about modifying the router so that utilizing the events would be optional, possibly through the introduction of something like different fire handlers (sort of like the firers used to be, but at a lower level, below the event handling). So we'd maybe think of it more like a meta-router, with the fire handlers each sort of being their own router.

As far as the reactors go, and the similarities between this UI and that which we've already created, I think that these do indeed have a lot of commonality, but some of the cause for duplication betwixt them really comes partly from the fact that the points and ranks APIs aren't composible themselves. Still, it might be possible to have a common underlying "reactor" API that could supply features to both of these APIs merely by composition, perhaps with the end in mind of eventually having such interfacing encompass the entirety of those APIs, rather than just being an extension of them.

And for the similarities in the UI in particular, we may find ourselves needing a more general "settings" or UI API of one sort or another, though perhaps that wouldn't have to be entirely fleshed out in the initial version.

The extensions model would also be useful, and perhaps even the ability to share "extensions" across these APIs.

So it does indeed seem that each of our event types actually needs its own API, but there are certain common patterns that we may want to flesh out into a meta API.

I'm wonder though whether now this will need to become more than a future possibility, and we'll actually end up having to develop this API alongside the events API. It isn't exactly an entirely different API after all, in the sense that it would be a different project, because it would be a child of the old points hooks, and that's what the hooks API project is supposed to be doing: creating the next generation of the points hooks. And it's possible that we'll find it difficult to be sure about whether we're developing a usable meta-API without some concrete implementation of this feature.

The problem with that is that we'd have to extend the amount of time that it takes to finish the initial version of this project out much further than we already have. Everything that we've built up to know has been mostly focused on the events, so although we wouldn't be starting from scratch exactly, there would be a lot of missing pieces to fill in.

So for the sake of getting the first round of this out as quickly as possible, it would be nice if we could just continue on with just the events API, but build the underlying APIs so that they can be reused to build that other part as well in the future. That is sort of what the intention of the current discussion was after all, that we make this forward composible for this feature, not that we implement it now.

If we could do that, and that's an if, we'd still have to continue to provide some way for the "toggle" type hooks to continue to work with the new events API, even though in the future that will be accomplished via this API. So we'd still have to build the infrastructure to make the toggle events work the way that we want them to, even though it would likely all get deprecated in the near future. I guess its a good thing that we've been thinking of moving that logic to extensions, since it shouldn't be part of the core of the API if its going to be removed later on. Still, getting it all working as we want it to will take work too, though I'm guessing it would still be less work than building the API that we've proposed here. But that is something that we have to weigh and consider.

JDGrimes commented 8 years ago

So here's what we'd need to do to get started:

Possibly we'd want to make the reaction API itself more generic, turning it into a sort of "database object" API or something. Although I have a feeling that we'll have trouble knowing exactly how to make the settings/reactor APIs more reusable until we actually implement this. So let's revisit that after we get the above done and see where we stand. I'll open a new ticket for each of those.

JDGrimes commented 8 years ago

So here's what we'd need to do to get started:

  • Disconnect action args from the events.

Considering the direction that we've ultimately decided to take things (see #129), I think that we already have what we need here, and it is called the entity API. :-)

  • Turn the router into more of a meta-router.

See #129.

-Disconnect the conditions from the reaction API.

This we might actually want to consider—in another ticket.

  • Add information to each action that describes what entities/entity children it changes, and how we can get the changelist at runtime.

See #118.

So I think that this ticket has run its course (and then some).

JDGrimes commented 8 years ago

For posterity, random notes that I had still lying around:


possibly leave conditions out of the initial version since they are causeing thie trouble? but thenwe sort of use conditions to determine whether an entity is even supposed to be counted or not (post must be published). so this might not solve anything.


an interesting idea is to go ahead and design the default possession arguments for each entity, and then use this information in some way to construct the events to be used until that api is fully developed. alshough we'd have to take care how we do that, because if the default possession args change, it coudl change event behavior undesireably.


what if we have a queue of actions performed by teh user that might cause a reverse and then ask them on the next page load if they want to reverse points transactions associated wit that? not really a greate idae, just a thought.


recomposing events may be undesirable for many existing users anyway. it would probably be better to inroduce new fire types, and use them for new reactions. But then we coudl proably maintain backward compatibility even with a forward composible API.


just registering the descriptive API is not enough, we have to also have a registry that tells us which entity modifications mean that its spam status was modifed (or some such API).


what about retroactivity and hte hit logs. all of this info woudln't ahve been logged about what entities were spam and all, and wouldn't that hinder backward recomposibiility as wel? but perhaps that simply means we couldn't do retroactive actions for legacy reactions, which makes sense.


maybe the reactors shoudjl be given the choice of which reactions shoudl respond. then they chould decide to have reactions respond to events that they weren't even hooked to, or I guess really what I mean is they could decide what actions a reaction wanted to listen for, and what response should be taken in regard to each one. a reaction might specify only that it wantst to listen to post publish, but if it specifies that it wants to be auto-reversed, teh reactor coudl automatically detect which other actions/events to listen to as well. that would let each reactor compose different actions as they saw fit. I guess really though there is no reason that we can't think o fthe events API the same way, just abstracted out of th reactors so that it will be reusable. if a reactor wants to use a diffrent sort of related actions, it can create a different fire type. but I think the difference in what I'm thinking of now is that it would be determined based on a descriptive hook arg API, so fire types wouldn't habe to be defiend befroehand ( inother words it woudl be forward recomposible). infact, the actual event that the reaction was tied to wouldn't be named liek they are now, but woudl be defiend the same way, but a particular change in a particular entity (or for the usage events a particular usage)


I think that to a certain degreee, the action are currently doing this. they don't offer teh information in raw form,b ut they do force the action to behave in a particular way, and only fire when a particular sort of change took place. making them more descrptive alone whouldn't solve our problem, because we'd still end up having to introduce new actions when the time came, actions that would describe themselves differently because they behaved differently. If we want to avoid that, we'd have to broaden the actions as well, not just make them describe themselves. (since many ofthe status transition actions are purposefully limited.)

post was_used for_display post status_changed to_publish post was_modified