Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actions with cancel and query operations, the meaning of input and output, dynamic href #1408

Open
egekorkan opened this issue Mar 1, 2022 · 5 comments · Fixed by #1447
Open
Assignees
Labels
Defer to TD 2.0 Has Use Case Potential The use case can be extracted and explained Needed by other TF An issue or UC from another TF to fullfill a requirement in their spec or gap PR needed Selected for Use Case The issue is relevant for the work and should move to an use case

Comments

@egekorkan
Copy link
Contributor

I could not really separate this into multiple issues. There are also multiple issues and PRs on this that I summarize below:

After #1208 is merged, we have cancelaction and queryaction operations. First of all, we need example TDs that have these operations. For the sake of this issue, they can look like the following (taken from https://github.com/w3c/wot-thing-description/tree/main/proposals/hypermedia-control-2):

{
 "@context": "https://www.w3.org/2022/wot/td/v1.1",
 "id": "urn:ex:thing",
 "actions": {
  "fade": {
   "input": {
    "type": "number",
    "description": "duration in ms"
   },
   "output": {},
   "forms": [
    {
     "href": "/fade",
     "op": "invokeaction",
     "htv:methodName": "POST"
    },
    {
     "href": "/fade/ongoing",
     "op": "queryaction",
     "htv:methodName": "GET"
    },
    {
     "href": "/fade/ongoing",
     "op": "cancelaction",
     "htv:methodName": "DELETE"
    }
   ]
  }
 }
}

Given that we cannot put dynamic/templating based URLs (i.e. "href": "/fade/{myActionID}",) without a way to understand how to provide the value of myActionID we have to have static href. Let's assume this is indeed the case until the Dynamic href chapter below.

There are still the following issues:

  1. When the request for invokeaction is responded with a payload, this payload corresponds to the schema defined by output. Given that the action can be synchronous and still provide queryaction and cancelaction, it means that the Consumer can query it while waiting for the response. If it is asynchronous, the Consumer will receive immediately a response (or no response at all) and will query the action later. This means that we have to add/change the following normative text:
    1. We need a way to say whether an action is synchronous or not. I propose to add the async keyword in the same level as idempotent like I have illustrated (pun intended) in Issue [vocabulary] Consider adding keyword to describe synchronous actions  #890 . The default value can be true. This allows the Consumer to decide when to do queryaction. If the action does not have cancelaction or queryaction, this information can be still useful since a property can give information on the action state as well, like a robot arm's position property when a movement-related action is invoked.
    2. This means that if an action is async, the output still corresponds to the response which is not necessarily the result of the physical action, see below.
  2. We have to assert that output is "related" with invokeaction and not with the other operations. The current text says Used to define the output data schema of the Action. Doing this change means that the response of queryaction can be different than invokeaction. We can say that it is the same and that a different output should be described by expectedResponse in some way.
  3. We have to assert that input is "related" to invokeaction and not with the other operations. This means that it is not possible to describe that one needs to provide input to cancelaction or queryaction.
  4. We have to add quite some explanatory text since currently, everything is very open to interpretation, which can lead to very different assumptions on how to implement this and lack of interoperability. These operations are also not tested in a plugfest.

Dynamic href

We can allow dynamic hrefs, they are actually already allowed via the use of uriVariables. Meaning that the following action is valid:

{
 "@context": "https://www.w3.org/2022/wot/td/v1.1",
 "id": "urn:ex:thing",
 "actions": {
  "fade": {
   "input": {
    "type": "number",
    "description": "duration in ms"
   },
   "output": {},
   "forms": [
    {
     "href": "/fade",
     "op": "invokeaction",
     "htv:methodName": "POST"
    },
    {
     "href": "/fade/ongoing",
     "op": "queryaction",
     "htv:methodName": "GET"
    },
    {
     "href": "/fade/ongoing",
     "op": "cancelaction",
     "htv:methodName": "DELETE"
    }
   ]
  }
 }
}

Of course, the myActionID uri variable contains out-of-band information since it can be in the body of the response of the invokeaction (where it would be in the output) or in the header, like it is done in the "core" profile https://w3c.github.io/wot-profile/#async-action-response . Maybe that is fine?

Notes

Sorry for noticing these implications this late. I did not see or forgot that we have cancelaction and queryaction now. My proposals are normative changes but it is technically still possible to have them after the WD. These will not have a big effect on testing since I have been providing TDs with the synchronous keyword for a couple of plugfests. See https://github.com/w3c/wot-testing/blob/main/events/2021.09.Online/TD/TDs/TUM/flask-based/DobotMagician.td.jsonld . The other changes do not introduce new keywords but change the current assertion text.

@egekorkan
Copy link
Contributor Author

Call of 02.03:

  • Regarding output: we should link it to the response of invokeaction. Maybe another keyword like queryAction
  • We can think of a state machine to explain how this behavior works. SCXML could be used to explain this in a standardized way. It is not clear that one should not cancel before invoking
  • We do not have an implementation of this since the profile will go to CR after TD.
  • We cannot do this without out-of-band information (like saying it is in a profile). A profile should be a subset of TD, so TD should be able to describe all possibilities. We should solve this first in the TD spec, profile should not have this for now. We can sync in the editor's call. @mlagally and @benfrancis please have look here.
  • Without dynamic ids, this can be already done in the Scripting API

@benfrancis
Copy link
Member

benfrancis commented Mar 4, 2022

@egekorkan Thanks for this detailed analysis.

I think most of these issues were already discussed in #302, where I highlighted the problem that it's really not clear how to use these operations without requiring additional out-of-band information like we have in the Core Profile. As you have identified, the most fundamental constraint is the lack of a way to clearly describe dynamic resources in a Thing Description.

With out-of-band information provided in a profile this is fine. For example, below are the action sections of the example Thing Description I've proposed for the Core Profile in w3c/wot-profile#91:

          "actions": {
            "fade": {
              "title": "Fade",
              "description": "Fade the lamp to a given level",
              "input": {
                "type": "object",
                "properties": {
                  "level": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "unit": "percent"
                  },
                  "duration": {
                    "type": "integer",
                    "minimum": 0,
                    "unit": "milliseconds"
                  }
                }
              },
              "forms": [{"href": "./actions/fade"}]
            }
          },
...
          "forms": [
            {
              "op": "queryallactions",
              "href": "./actions"
            },
          ]

All that's needed is to define a top level endpoints for actions and the protocol binding section of the profile defines the rest.

Trying to express the Core Profile's full protocol binding for invokeaction, queryaction, cancelaction and queryallactions declaratively in a Thing Description (for Consumers which don't implement the Core Profile) is very difficult. Below are the action sections of an example Thing Description I have proposed in w3c/wot-profile#91 with my best attempt at illustrating what this might look like.

(Note that the intention of this example is not that a Thing would ever expose a Thing Description like this, it's more to illustrate to what extent implementing the Core Profile can simplify the implementation of complex interactions for conformant Producers and Consumers and to illustrate the Core Profile's protocol binding in the form of an informative Thing Description).

     "actions": {
        "fade": {
          "title": "Fade",
          "description": "Fade the lamp to a given level",
          "input": {
            "type": "object",
            "properties": {
              "level": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100,
                "unit": "percent"
              },
              "duration": {
                "type": "integer",
                "minimum": 0,
                "unit": "milliseconds"
              }
            }
          },
          "forms": [
            {
              "op": "invokeaction",
              "href": "./actions/fade",
              "htv:methodName": "POST",
              "contentType": "application/json",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "htv:statusCodeNumber": 201,
                "htv:headers": [
                  {
                    "htv:fieldName": "Location",
                    "htv:fieldValue": "/fade/{id}"
                  }
                ],
                "contentType": "application/json",
                "schema": "actionStatus"
              },
              "additionalResponses": [
                {
                  "htv:statusCodeNumber": 200,
                  "htv:headers": [
                    {
                      "htv:fieldName": "Location",
                      "htv:fieldValue": "/fade/{id}"
                    }
                  ],
                  "contentType": "application/json",
                  "schema": "actionStatus"
                }
              ]
            },
            {
              "op": "queryaction",
              "href": "./actions/fade/{id}",
              "htv:methodName": "GET",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "contentType": "application/json",
                "schema": "actionStatus"
              }
            },
            {
              "op": "cancelaction",
              "href": "./actions/fade/{id}",
              "htv:methodName": "DELETE",
              "contentType": "application/json"
            }
          ],
          "uriVariables": {
            "id": {
              "type": "string",
              "description": "identifier of action request"
            }
          }
        }
      }
      ...
      "forms" [
        {
          "op": "queryallactions",
          "href": "./actions",
          "htv:methodName": "GET",
          "htv:headers": [{
            "htv:fieldName": "Accept",
            "htv:fieldValue": "application/json"
          }],
          "response": {
            "contentType": "application/json",
            "schema": "actionStatusList"
          }
        }
      ]
      ...
      "schemaDefinitions": {
        "actionStatus": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "pending",
                "running",
                "completed",
                "failed"
              ]
            },
            "error": {
              "type": "object"
            },
            "href": {
              "type": "string",
              "format": "uri",
              "const": "./actions/fade/{id}"
            }
          },
          "required": [
            "status"
          ]
        },
        "actionStatusList": {
          "type": "object",
          "additionalProperties": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "running",
                    "completed",
                    "failed"
                  ]
                },
                "error": {
                  "type": "object"
                },
                "href": {
                  "type": "string",
                  "format": "uri",
                  "const": "./actions/fade/{id}"
                }
              },
              "required": [
                "status"
              ]
            }
          }
        }  
      }

You can see how complicated this gets, and there are still some open issues I haven't been able to resolve due to limitations of the Thing Description specification.

As we discussed in #302 it's also still not clear how a Consumer (without the out-of-band information in the profile) would automatically be able to understand how the value of the {id} URI variables is meant to be re-used between interaction affordances.

In #302 (comment) I suggested two potential paths forward for dealing with these more complex use cases:

  1. Continue to try to define a vocabulary for describing dynamic resources, and the relationships between them, in Thing Descriptions. Perhaps using semantic annotations and new operation types to deal with collections of resources, like queryactionlist.

  2. Accept that there are limitations to the complexity of interactions that can automatically be inferred by Consumers from Forms in a Thing Description. Have Web Things provide enough metadata in a Thing Description for a Consumer to carry out basic operations (like invokeaction), and rely on out-of-band information (e.g. in the prose of a profile specification) to define more complex interactions like action queues. That could mean for example that any WoT Consumer could invoke an action (and perhaps query and cancel a single instance), but only Consumers which implement the profile specification are able to deal with dynamic resources in an action queue.

It could be possible for us to try both:

  1. First define an HTTP API for action queues in the Core Profile protocol binding, which works by applying a set of defaults/assumptions to a single endpoint provided for an ActionAffordance in a Thing Description

  2. Then try to define vocabulary to describe that (and other) APIs declaratively in a Thing Description, such that any WoT Consumer could theoretically use the full set of operations without implementing the Core Profile

What we did in the end was to define the HTTP API for action queues in the Core Profile, and add the basic op values needed to include those operations in a Thing Description. What we didn't do is add a vocabulary to Thing Descriptions for describing dynamic resources, so that limitation still exists.

Basically the conclusion in the Profile task force was that it's fine for a profile to provide out-of-band information which goes beyond what can be described declaratively in a Thing Description, and that the Thing Description specification should eventually try to catch up.

To address your proposals for normative changes...

@egekorkan wrote:

  1. When the request for invokeaction is responded with a payload, this payload corresponds to the schema defined by output.

This isn't always the case in the Core Profile protocol binding.

The ActionStatus object contains the output data of a completed action in both synchronous and asynchronous cases. In the synchronous case this object is provided in the response to the invokeaction request and in the asynchronous case the object is provided in response to a queryaction operation.

In other words, the output data schema always describes the output of the physical action, which is not necessarily the response to the invokeaction request.

Edit: Also, what would this mean in a protocol where there is no response to an invokeaction request, e.g. a WebSocket protocol?

Given that the action can be synchronous and still provide queryaction and cancelaction, it means that the Consumer can query it while waiting for the response. If it is asynchronous, the Consumer will receive immediately a response (or no response at all) and will query the action later. This means that we have to add/change the following normative text:

1. We need a way to say whether an action is synchronous or not. I propose to add the async keyword in the same level as idempotent like I have illustrated (pun intended) in Issue https://github.com/w3c/wot-thing-description/issues/890 .

I'm not opposed to this, but does this keyword really make any sense beyond HTTP? Also note that in my example above the action can be used both synchronously and asynchronously! What value would you use then?

 2.  The default value can be true. This allows the Consumer to decide when to do queryaction. If the action does not have cancelaction or queryaction, this information can be still useful since a property can give information on the action state as well, like a robot arm's position property when a movement-related action is invoked.

I definitely don't think actions should be asynchronous by default. Because a) This is the more complex case and b) the distinction doesn't even make sense in some protocols.

This means that if an action is async, the output still corresponds to the response which is not necessarily the result of the physical action, see below.

As mentioned above, this conflicts with the current specification of the Core Profile where it is always the result of the physical action.

  1. We have to assert that output is "related" with invokeaction and not with the other operations. The current text says Used to define the output data schema of the Action. Doing this change means that the response of queryaction can be different than invokeaction. We can say that it is the same and that a different output should be described by expectedResponse in some way.

I do this the other way around in the examples above. In the asynchronous case, the response member is used in the Form of the invokeaction operation to describe the payload of the invokeaction response, and the output data schema is used in the queryaction response instead. (Note there's still the issue with no schema member in ExpectedResponse).

  1. We have to assert that input is "related" to invokeaction and not with the other operations. This means that it is not possible to describe that one needs to provide input to cancelaction or queryaction.

That's OK with me. Note that in the examples above the "input" to cancelaction and queryaction is just the ID of the action instance, which is provided in the URI of the request.

We have to add quite some explanatory text since currently, everything is very open to interpretation, which can lead to very different assumptions on how to implement this and lack of interoperability.

I agree they are open to interpretation, as is illustrated by the differences in the approaches you and I took described above. However, I think this applies to lots of other existing operations too. Explanatory text would be welcome, but can only go so far.

These operations are also not tested in a plugfest.

I hope to have an implementation of the Core Profile in WebThings Gateway to test (properties and events are already implemented, actions still need some work). However, my intention is to use the simplified Thing Description style show in my first example and rely on the Core Profile protocol binding to describe the rest. That would mean that only Consumers which implement the Core Profile would be able to use the queryaction and cancelaction operations. Other Consumers would only be able to use invokeaction. Until Thing Descriptions are expressive enough to describe the dynamic resources used in an asynchronous action queue, I think this is a limitation we have to live with.

@benfrancis
Copy link
Member

@egekorkan wrote:

Call of 02.03:

  • We cannot do this without out-of-band information (like saying it is in a profile). A profile should be a subset of TD, so TD should be able to describe all possibilities. We should solve this first in the TD spec, profile should not have this for now. We can sync in the editor's call. @mlagally and @benfrancis please have look here.

I agree that some of these features (e.g. an asynchronous action queue) are very difficult to describe without out-of-band information (e.g. in a profile), and that ideally it would be possible to describe all possible interactions in a Thing Description. However, as we've known for a long time there are already many existing IoT APIs (e.g. the Web Thing API) which can't be completely described by a WoT Thing Description because a Thing Description is still not (and in my opinion can never be) expressive enough to cover all possible use cases.

The fact that a profile is able to provide a concrete protocol binding which goes beyond what is possible to describe declaratively in a Thing Description alone is what has made it possible for the WebThings platform to move towards W3C compliance. If the action queue part of the protocol binding has to be removed from the Core Profile because it can't also be described declaratively in a Thing Description, then there is no way for the the WebThings platform to be made W3C compliant without also removing features (i.e. action queues) from the platform. That doesn't seem acceptable to me.

In conclusion:

  • Yes there are still limitations on what can be described using a declarative protocol binding in a Thing Description (e.g. it's still difficult to describe dynamic resources) but that has always been the case.
  • Adding the queryaction and cancelaction operations is a step towards making Thing Descriptions more expressive, and there are other existing operations (e.g. observeproperty/unobserveproperty) which are arguably just as open to interpretation.
  • I agree that examples would help, e.g. the synchronous example @egekorkan provided (I'd be interested to see your asynchronous example, because I think you accidentally just copy & pasted the synchronous one).
  • I disagree that the concrete protocol bindings of profiles should be constrained by what is possible to describe in declarative protocol bindings in Thing Descriptions, because declarative protocol bindings suffer from fundamental limitations which can not easily be solved. Profiles can actually help solve that problem.

@sebastiankb
Copy link
Contributor

from today's TD call:

  • so far the query operations are kept in the spec
  • an additional text (note box) will provided that this op values are currently used by the WoT Profile document
  • most likely the values will be marked as at risk after the TestFest and we need to evaluate if we can keep those values in the TD 1.1 document at all
  • TD 2.0 should have a per-descriptive solution in the document

@JKRhb
Copy link
Member

JKRhb commented Mar 30, 2022

Oh, sorry, this issue needs to be reopened, I linked the wrong issue in #1447

@benfrancis benfrancis reopened this Mar 30, 2022
@egekorkan egekorkan added Has Use Case Potential The use case can be extracted and explained Selected for Use Case The issue is relevant for the work and should move to an use case labels Feb 14, 2024
@danielpeintner danielpeintner added the Needed by other TF An issue or UC from another TF to fullfill a requirement in their spec or gap label Feb 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Defer to TD 2.0 Has Use Case Potential The use case can be extracted and explained Needed by other TF An issue or UC from another TF to fullfill a requirement in their spec or gap PR needed Selected for Use Case The issue is relevant for the work and should move to an use case
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants