From 1aee97c9ac4168f5fe3607ffb0925bb3164c0d4f Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 10:32:10 -0800 Subject: [PATCH 01/16] fork: ucan-invocation spec --- .github/workflows/words-to-ignore.txt | 35 + invocation.md | 1345 +++++++++++++++++++++++++ 2 files changed, 1380 insertions(+) create mode 100644 invocation.md diff --git a/.github/workflows/words-to-ignore.txt b/.github/workflows/words-to-ignore.txt index 5dcf65e..d803832 100644 --- a/.github/workflows/words-to-ignore.txt +++ b/.github/workflows/words-to-ignore.txt @@ -39,6 +39,41 @@ URIs interop permissionless stateful +pipelining +RPC +Akiko +pre-vacation +referentially +Akiko's +invoker +de +facto +DAG-JSON +DAG-CBOR +Varsig +delegator +FieldDelegationTaskDelegator +cryptographically +invariants +SemVer-formatted +SemVer +struct +enum +UCAN-IPLD +WebAssembly +pipelined +inlines +inlined +atomicity +parallelize +Invokers +dataflow +CapTP +Acknowledgements +canonicalized +OAuth +simple-but-evolvable +implicits # actually correct reimagine diff --git a/invocation.md b/invocation.md new file mode 100644 index 0000000..b59329f --- /dev/null +++ b/invocation.md @@ -0,0 +1,1345 @@ +# UCAN Invocation Specification v0.1.0 + +## Editors + +- [Brooklyn Zelenka](https://github.com/expede/), [Fission](https://fission.codes/) + +## Authors + +- [Brooklyn Zelenka](https://github.com/expede/), [Fission](https://fission.codes/) +- [Irakli Gozalishvili](https://github.com/Gozala), [DAG House](https://dag.house/) + +## Depends On + +- [DAG-CBOR](https://ipld.io/specs/codecs/dag-cbor/spec/) +- [UCAN](https://github.com/ucan-wg/spec/) +- [UCAN-IPLD](https://github.com/ucan-wg/ucan-ipld/) +- [Varsig](https://github.com/ChainAgnostic/varsig/) + +# 0 Abstract + +UCAN Invocation defines a format for expressing the intention to run delegated capabilities from a UCAN, the attested receipts from an execution, and how to extend computation via promise pipelining. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). + +# 1 Introduction + +> Just because you can doesn't mean that you should +> +> — Anonymous + +UCAN is a chained-capability format. A UCAN contains all of the information that one would need to perform some task, and the provable authority to do so. This begs the question: can UCAN be used directly as an RPC language? + +Some teams have had success with UCAN directly for RPC when the intention is clear from context. This can be successful when there is more information on the channel than the UCAN itself (such as an HTTP path that a UCAN is sent to). However, capability invocation contains strictly more information than delegation: all of the authority of UCAN, plus the command to perform the task. + +## 1.1 Intuition + +## 1.1.1 Car Keys + +Consider the following fictitious scenario: + +Akiko is going away for the weekend. Her good friend Boris is going to borrow her car while she's away. They meet at a nearby cafe, and Akiko hands Boris her car keys. Boris now has the capability to drive Akiko's car whenever he wants to. Depending on their plans for the rest of the day, Akiko may find Boris quite rude if he immediately leaves the cafe to go for a drive. On the other hand, if Akiko asks Boris to run some last minute pre-vacation errands for that require a car, she may expect Boris to immediately drive off. + +## 1.1.2 Lazy vs Eager Evaluation + +In a referentially transparent setting, the description of a task is equivalent to having done so: a function and its results are interchangeable. [Programming languages with call-by-need semantics](https://en.wikipedia.org/wiki/Haskell) have shown that this can be an elegant programming model, especially for pure functions. However, _when_ something will run can sometimes be unclear. + +Most languages use eager evaluation. Eager languages must contend directly with the distinction between a reference to a function and a command to run it. For instance, in JavaScript, adding parentheses to a function will run it. Omitting them lets the program pass around a reference to the function without immediately invoking it. + +```js +const message = () => alert("hello world") +message // Nothing happens +message() // A message interups the user +``` + +Delegating a capability is like the statement `message`. Task is akin to `message()`. It's true that sometimes we know to run things from their surrounding context without the parentheses: + +```js +;[1, 2, 3].map(message) // Message runs 3 times +``` + +However, there is clearly a distinction between passing a function and invoking it. The same is true for capabilities: delegating the authority to do something is not the same as asking for it to be done immediately, even if sometimes it's clear from context. + +## 1.2 Separation of Concerns + +Information about the scheduling, order, and pipelining of tasks is orthogonal to the flow of authority. An agent collaborating with the original executor does not need to know that their call is 3 invocations deep; they only need to know that they been asked to perform some task by the latest invoker. + +As we shall see in the [discussion of promise pipelining](#6-promise-pipelining), asking an agent to perform a sequence of tasks before you know the exact parameters requires delegating capabilities for all possible steps in the pipeline. Pulling pipelining detail out of the core UCAN spec serves two functions: it keeps the UCAN spec focused on the flow of authority, and makes salient the level of de facto authority that the executor has (since they can claim any value as having returned for any step). + +``` + ────────────────────────────────────────────Time──────────────────────────────────────────────────────► + +┌──────────────────────────────────────────Delegation─────────────────────────────────────────────────────┐ +│ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ │ │ │ │ │ │ │ │ │ │ +│ │ Alice ├──►│ Bob ├──►│ Carol ├────────►│ Dan ├───────────────►│ Erin │ │ +│ │ │ │ │ │ │ │ │ │ │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +┌──────────────────────────────────────────Invocation─────────────────────────────────────────────────────┐ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ │ │ │ │ +│ │ Carol ╞═══All══►│ Dan │ │ +│ │ │ │ │ │ +│ └─────────┘ └─────────┘ │ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ │ │ │ │ +│ │ Dan ╞═══════════Update DB═════════►│ Erin │ │ +│ │ │ │ │ │ +│ └─────────┘ └─────────┘ │ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ │ │ │ │ +│ │ Dan ╞═══Read Email══►│ Erin │ │ +│ │ │ ▲ │ │ │ +│ └─────────┘ ┆ └─────────┘ │ +│ With │ +│ Result │ +│ ┌─────────┐ Of ┌─────────┐ │ +│ │ │ ┆ │ │ │ +│ │ Dan ╞════Set DNS══►│ Erin │ │ +│ │ │ │ │ │ +│ └─────────┘ └─────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +``` + +## 1.3 A Note On Serialization + +The JSON examples below are given in [DAG-JSON](https://ipld.io/docs/codecs/known/dag-json/), but UCAN Task is actually defined as IPLD. This makes UCAN Task agnostic to encoding. DAG-JSON follows particular conventions around wrapping CIDs and binary data in tags like so: + +```json +// CID +{"/": "Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD"} + +// Bytes (e.g. a signature) +{"/": {"bytes": "s0m3Byte5"}} +``` + +This format help disambiguate type information in generic DAG-JSON tooling. However, your presentation need not be in this specific format, as long as it can be converted to and from this cleanly. As it is used for the signature format, DAG-CBOR is RECOMMENDED. + +## 1.4 Signatures + +All payloads described in this spec MUST be signed with a [Varsig](https://github.com/ChainAgnostic/varsig/). + +# 2 High-Level Concepts + +## 2.1 Roles + +Task adds two new roles to UCAN: invoker and executor. The existing UCAN delegator and delegate principals MUST persist to the invocation. + +| UCAN Field | Delegation | Task | +| ---------- | -------------------------------------- | ------------------------------- | +| `iss` | Delegator: transfer authority (active) | Invoker: request task (active) | +| `aud` | Delegate: gain authority (passive) | Executor: perform task (active) | + +### 2.1.1 Invoker + +The invoker signals to the executor that a task associated with a UCAN SHOULD be performed. + +The invoker MUST be the UCAN delegator. Their DID MUST be authenticated in the `iss` field of the contained UCAN. + +### 2.1.2 Executor + +The executor is directed to perform some task described in the UCAN by the invoker. + +The executor MUST be the UCAN delegate. Their DID MUST be set the in `aud` field of the contained UCAN. + +## 2.2 Components + +![](./diagrams/concepts.svg) + +### 2.2.1 Closure + +A [Closure](#3-closure) is like a deferred function application: a request to perform some action on a resource with specific inputs. + +### 2.2.2 Task + +A [Task](#4-task) extends a Closure with additional metadata that is not used to describe the meaning of the computation or effect to be run. + +### 2.2.3 Batch + +A [Batch](#5-batch) is a way of requesting more than one action at once. + +### 2.2.4 Invocation + +An [Invocation](#6-invocation) is the cryptographically signed container for a Batch. This is the step that "forces" the "deferred" Closure. + +### 2.2.5 Pointers + +An [Invocation Pointer](#7-pointer) identifies a specific invocation. An Invoked Task Pointer points to a unique Task inside an Invocation. + +### 2.2.6 Result + +A [Result](#8-result) is the output of a Closure. + +### 2.2.7 Receipt + +A [Receipt](#9-receipt) describes the output of an invocation, referenced by its Invocation Pointer. + +### 2.2.8 Promise + +A [promise](#10-promise) is a reference to the receipt of an action that has yet to return a receipt. + +## 2.3 IPLD Schema + +```ipldsch +type Closure struct { + with URI + do Ability + inputs Any +} + +type Task struct { + with URI + do Ability + inputs Any + meta {String : Any} (implicit {}) +} + +type Batch union { + | Named {String : Task} + | List [Task] +} + +type Invocation struct { + uiv SemVer + run Batch + prf [&UCAN] + nnc String + meta {String : Any} (implicit {}) + sig Varsig +} + +type InvocationPointer union { + | "/" -- Relative to the current invocation + | &Invocation +} + +type InvokedTaskPointer struct { + envl InvocationPointer + label String +} representation tuple + +type Receipt struct { + ran &InvocationPointer + out {String : Result} + rec {String : &Receipt} + meta {String : Any} + sig Varsig +} + +type Result union { + | Any ("ok") -- Success + | Any ("err") -- Failure +} representation keyed + +type Promise union { + | InvokedTaskPointer "promise/ok" + | InvokedTaskPointer "promise/err" + | InvokedTaskPointer "promise/*" +} representation keyed +``` + +# 3 Closure + +A Closure is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, inputs)` triple. The `inputs` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. + +Using the JavaScript analogy from the introduction, a Closure is similar to wrapping a call in an anonymous function: + +```json +{ + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": ["bob@example.com", "carol@example.com"], + "subject": "hello", + "body": "world" + } +} +``` + +```js +// Pseudocode +;() => + msg.send("mailto:alice@example.com", { + to: ["bob@example.com", "carol@example.com"], + subject: "hello", + body: "world", + }) +``` + +Later, when we explore [Promises](# 10-promise), this also includes capturing the promise: + +```json +{ + "mailingList": { + "with": "https://example.com/mailinglist", + "do": "crud/read" + }, + "sendEmail": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": {"promise/*": ["/", "get-mailing-list"]} + "subject": "hello", + "body": "world" + } + } +} +``` + +```js +// Pseudocode +const mailingList = crud.read("https://exmaple.com/mailinglist", {}) // ---┐ +// │ +const sendEmail = () => + msg.send("mailto:alice@example.com", { + // │ + to: mailingList, // <----------------------------------------------------┘ + subject: "hello", + body: "world", + }) +``` + +## 3.1 Fields + +```ipldsch +type Closure struct { + with URI + do Ability + inputs Any +} +``` + +### 3.1.1 Resource + +The `with` field MUST contain the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the resource being accessed. If the resource being accessed is some static data, it is RECOMMENDED to reference it by the [`data`](https://en.wikipedia.org/wiki/Data_URI_scheme), [`ipfs`](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls), or [`magnet`](https://en.wikipedia.org/wiki/Magnet_URI_scheme) URI schemes. + +### 3.1.2 Ability + +The `do` field MUST contain a [UCAN Ability](https://github.com/ucan-wg/spec/#23-ability). This field can be thought of as the message or trait being sent to the resource. + +### 3.1.3 Inputs + +The `inputs` field MUST contain any arguments expected by the URI/Ability pair. This MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. + +### 3.2 DAG-JSON Examples + +Interacting with an HTTP API: + +```json +{ + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } +} +``` + +Sending Email: + +```json +{ + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + } +} +``` + +Running WebAssembly from binary: + +```json +{ + "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", + "do": "wasm/run", + "inputs": { + "func": "add_one", + "args": [42] + } +} +``` + +Executing all of the capabilities in a UCAN directly: + +```json +{ + "with": "ipfs://bafkreiemaanh3kxqchhcdx3yckeb3xvmboztptlgtmnu5jp63bvymxtlva", + "do": "ucan/run", + "inputs": null +} +``` + +# 4 Task + +A Task is subtype of a [Closure](#3-closure), adding an OPTIONAL metadata field. If not present, the `meta` field defaults to an empty map. A Task can be trivially converted to a Closure by removing the `meta` field. + +```ipldsch +type Task struct { + with URI + do Ability + inputs Any + meta {String : Any} (implicit {}) +} +``` + +## 4.1 Fields + +### 4.1.1 Closure Fields + +The `with`, `do`, and `inputs` field from [Closure](#3-closure) remain unchanged. + +### 4.1.2 Metadata + +The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. + +## 4.2 DAG-JSON Examples + +Sending Email: + +```json +{ + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + } +} +``` + +Running WebAssembly from binary: + +```json +{ + "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", + "do": "wasm/run", + "inputs": { + "func": "add_one", + "args": [42] + }, + "meta": { + "dev/notes": "The standard Wasm demo", + "ipvm/verification": "attestation", + "ipvm/resources": { + "gas": 5000 + } + } +} +``` + +# 5 Batch + +A Batch is a collection of Tasks, either as a `List` (array) or `Named` (map). In many situations, sending multiple requests in a Batch is more efficient than one-at-a-time. + +A `List` is sugar for a `Named` map, where the keys are the array index number as strings. + +```ipldsch +type Batch union { + | Named {String : Task} + | List [Task] +} +``` + +## 5.1 Fields + +Each Task in a Batch contains MAY be referenced by a string label. + +## 5.2 DAG-JSON Examples + +### 5.2.1 Named + +```json +{ + "left": { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + "right": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + } + } +} +``` + +### 5.2.2 List + +```json +[ + { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + } + } +] +``` + +# 6 Invocation + +As [noted in the introduction](#112-lazy-vs-eager-evaluation), there is a difference between a reference to a function and calling that function. [Closures](#3-closure) and [Tasks](#4-task) are not executable until they have been provided provable authority from the [Invoker](#211-invoker) (in the form of UCANs), and signed with the Invoker's private key. + +## 6.1 IPLD Schema + +```ipldsch +type Invocation struct { + uiv SemVer + run Batch + prf [&UCAN] + nnc String + meta {String : Any} (implicit {}) + sig Varsig +} +``` + +## 6.2 Fields + +An Invocation authorizes one or more Tasks to be run. There are a few invariants that MUST hold between the `run`, `prf` and `sig` fields: + +- All of the `prf` UCANs MUST list the Invoker in their `iss` +- The `sig` field MUST be produced by the Invoker +- All of the `run` Tasks MUST be provably authorized by the UCANs in the `prf` field +- The Executor(s) MUST be listed in the `aud` field of a UCAN that grants it the authority to perform some action on a resource, or be the root authority for it + +### 6.2.1 UCAN Task Version + +The `uiv` field MUST contain the SemVer-formatted version of the UCAN Task Specification that this struct conforms to. + +### 6.2.2 Task + +The `run` field MUST contain a link to the [Task](#31-single-invocation) itself. + +### 6.2.3 Proofs + +The `prf` field MUST contain links to any UCANs that provide the authority to run the actions. All of the outermost `aud` fields MUST be set to the [Executor](#212-executor)'s DID. All of the outermost `iss` field MUST be set to the [Invoker](#211-invoker)'s DID. + +### 6.2.4 Nonce + +The `nnc` field MUST include a random nonce field expressed in ASCII. This field ensures that multiple invocations are unique. + +### 6.2.5 Metadata + +If present, the OPTIONAL `meta` map MAY contain free form fields. This provides a place for extension of the invocation type. + +Data inside the `meta` field SHOULD NOT be used for [Receipts](#9-receipt). + +### 6.2.6 Signature + +The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `prf`, and `nnc` fields. + +## 6.3 DAG-JSON Examples + +```json +{ + "uiv": "0.1.0", + "nnc": "6c*97-3=", + "run": { + "left": { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + "right": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + } + } + }, + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "meta": { + "notes/personal": "I felt like making an invocation today!", + "ipvm/config": { + "time": [5, "minutes"], + "gas": 3000 + } + }, + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +# 7 Pointer + +An Invocation Pointer references a specific [Invocation](#6-invocation), either directly by CID (absolute), or from inside the Invocation itself (relative). + +```ipldsch +type InvocationPointer union { + | "/" -- Relative to the current invocation + | &Invocation +} +``` + +An Invoked Task Pointer references a specific Task inside a Batch, by the name of the label. If the Batch is unlabelled (a `List`), then the index represented as a string MUST be used. + +```ipldsch +type InvokedTaskPointer struct { + envl InvocationPointer + label String +} representation tuple +``` + +## 7.2 DAG-JSON Examples + +### 7.2.1 Relative + +#### 7.2.1.1 Named + +This relative pointer: + +```json +["/", "some-label"] +``` + +Will select the marked fields in these Named invocations: + +```json +{ + "uiv": "0.1.0", + "nnc": "6c*97-3=", + "run": { + "some-label": { + // <- Selects this + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + "some-other-label": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high", + "dev/notes": { + "select-task": ["/", "some-label"] // <- Pointer here + } + } + } + }, + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +```json +{ + "uiv": "0.1.0", + "nnc": "myNonce529", + "run": { + "some-label": { // <- Selects this + "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", + "do": "wasm/run", + "inputs": { + "func": "add_one", + "args": [42] + } + } + }, + "meta": { + "dev/notes": { + "select-task": ["/", "some-label"] // <- Pointer here + } + } + "prf": [{"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"}], + "sig": {"/": {"bytes:": "LcZglimIwQ58T0rnkErYshq2S8MMF9G/zRqYXv/PmXs="}} +} +``` + +### 7.2.1.1 List + +This local pointer: + +```json +["/", "1"] +``` + +Will select the marked fields in this List invocation: + +```json +{ + "uiv": "0.1.0", + "nnc": "6c*97-3=", + "run": [ + { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high", + "dev/notes": { + "select-task": ["/", "0"] // <- Pointer here + } + } + }, + // Selects this + // vvvvvvvvvvvv + { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + } + ], + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +### 7.2.2 Absolute + +#### 7.2.2.1 Named + +This absolute pointer: + +```json +[ + { "/": "bafkreiff4alf4rdi5mqg4fpxiejgotcnf2zksqanp5ctwzinmqyf7o3i2e" }, + "some-label" +] +``` + +Will select the marked field in this Named invocation: + +```json +// CID = bafkreiff4alf4rdi5mqg4fpxiejgotcnf2zksqanp5ctwzinmqyf7o3i2e +{ + "uiv": "0.1.0", + "nnc": "6c*97-3=", + "run": { + "some-label": { + // <- Selects this + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + "some-other-label": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high", + "dev/notes": { + "select-task": ["/", "some-label"] // <- Pointer here + } + } + } + }, + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +#### 7.2.2.1 List + +This absolute pointer: + +```json +[{ "/": "bafkreiew2p74l7bq3hnllbduzagdcezlab54ko4lpw72mfcvilh4ov2hkq" }, "1"] +``` + +Will select the marked field in this List invocation: + +```json +// CID = bafkreiew2p74l7bq3hnllbduzagdcezlab54ko4lpw72mfcvilh4ov2hkq +{ + "uiv": "0.1.0", + "nnc": "6c*97-3=", + "run": [ + { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high", + "dev/notes": { + "select-task": ["/", "0"] // <- Pointer here + } + } + }, + // Selects this + // vvvvvvvvvvvv + { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + } + ], + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +# 8 Result + +A Result records the output of a [Task](#4-task), as well as its success or failure state. + +## 8.1 Variants + +```ipldsch +type Result union { + | Any ("ok") -- Success + | {String: Any} ("err") -- Failure +} representation keyed +``` + +### 8.1.1 Success + +The success branch MUST contain the value returned from a successful Task wrapped in the `"ok"` tag. The exact shape of the returned data is left undefined to allow for flexibility in various Task types. + +```json +{ "ok": 42 } +``` + +### 8.1.2 Failure + +The failure branch MAY contain detail about why execution failed wrapped in the `"err"` tag. It is left undefined in this specification to allow for Task types to standardize the data that makes sense in their contexts. + +If no information is available, this field SHOULD be set to `{}`. + +```json +{ + "err": { + "dev/reason": "unauthorized", + "http/status": 401 + } +} +``` + +# 9 Receipt + +An Invocation Receipt is an attestation of the Result of an Invocation. A Receipt MUST be signed by the Executor (the `aud` of the associated UCANs). + +**NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. + +Receipts MUST use the same version as the invocation that they contain. + +## 9.1 Fields + +```ipldsch +type Receipt struct { + ran &InvokedTaskPointer + out {String : Result} + rec {String : &Receipt} + meta {String : Any} + sig Varsig +} +``` + +### 9.1.1 Task + +The `inv` field MUST include a link to the Task that the Receipt is for. + +### 9.1.2 Output + +The `out` field MUST contain the output of steps of the call graph, indexed by the task name inside the invocation. The `out` field MAY omit any tasks that have not yet completed, or results which are not public. An `Task` may be associated to zero or more `Receipts`. + +A `Result` MAY include recursive `Receipt` CIDs in on the `Success` branch. As a Task may require subdelegation, the OPTIONAL `rec` field MAY be used to include recursive `Receipt`s. + +### 9.1.3 Recursive Receipt + +In the case that an Invocation was subdelegated to another Executor and the Result bubbled up, a recursive Receipt SHOULD be included in the `rec` field. + +### 9.1.4 Metadata Fields + +The metadata field MAY be omitted or used to contain additional data about the receipt. This field MAY be used for tags, commentary, trace information, and so on. + +### 9.1.5 Signature + +The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `out`, and `meta` fields. The signature MUST be generated by the Executor, which means the public key in the `aud` field of the UCANs backing the Task. + +## 9.2 DAG-JSON Examples + +```json +{ + "ran": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, + "out": { + "ok": [ + { + "from": "bob@example.com", + "text": "Hello world!" + }, + { + "from": "carol@example.com", + "text": "What's up?" + } + ] + }, + "meta": { + "time": [400, "hours"], + "retries": 2 + }, + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt_VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } +} +``` + +# 10 Promise + +> Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips +> +> — [Mark Miller](https://github.com/erights), [Robust Composition](http://www.erights.org/talks/thesis/markm-thesis.pdf) + +There MAY not be enough information to described an Invocation at creation time. However, all of the information required to construct the next request in a sequence MAY be available in the same Batch, or in a previous (but not yet complete) Invocation. Waiting for each request to complete before proceeding to the next task has a performance impact due to the amount of latency. [Promise pipelining](http://erights.org/elib/distrib/pipeline.html) is a solution to this problem: by referencing a prior invocation, a pipelined invocation can direct the executor to use the output of earlier invocations into the input of a later one. This liberates the invoker from waiting for each step. + +A Promise is a placeholder value MAY be used as a variable placeholder for a concrete value in a [Closure](#3-closure), waiting on a previous step to complete. + +One way of seeing the names in a [`Batch`](#5-batch) is as variables for the return of each Closure. These can now be referenced by other Closures. + +For example, consider the following batch: + +```json +{ + "run": { + "create-draft": { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything..." + } + } + }, + "get-editors": { + "with": "https://example.com/users/editors", + "do": "crud/read" + }, + "notify": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": { "promise/ok": ["/", "get-editors"] }, + "subject": "Coffee", + "body": { "promise/ok": ["/", "create-draft"] } + } + } + } +} +``` + +By analogy, this can be interpreted roughly as follows: + +```js +const createDraft = crud.create("https://example.com/blog/posts", { + payload: { + title: "How UCAN Tasks Changed My Life", + body: "This is the story of how one spec changed everything...", + }, +}) + +const getEditors = crud.read("https://example.com/users/editors") + +const notify = msg.send("mailto:akiko@example.com", { + to: await createDraft, + subject: "Coffee", + body: await getEditors, +}) +``` + +While a Promise MAY be substituted for any field in a Closure, substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. + +After resolution, the Task MUST be validated against the UCANs known to the Executor. A Promise resolved to a Task that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. + +Promises MAY be used inside of a single Invocation's Closures, or across multiple Invocations, and MAY even be across multiple Invokers. As long as the pointer can be resolved, any invoked Task MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). + +A Promise MUST resolve to a [Result](#8-result). If a particular branch's value is required to be unwrapped, the Result tag (`ok` or `err`) MAY be supplied. + +## 10.1 Enum & Fields + +The following describe a pointer to the eventual value in a Promise, on either branch (`promise/*`), or specifically the success (`promise/ok`) or failure (`promise/err`) branches. + +```ipldsch +type Promise union { + | InvokedTaskPointer "promise/*" + | InvokedTaskPointer "promise/ok" + | InvokedTaskPointer "promise/err" +} representation keyed +``` + +If there are dependencies or ordering required, then you need a promise pipeline + +## 10.2 Pipelines + +Pipelining uses promises as inputs to determine the required dataflow graph. The following examples both express the following dataflow graph: + +### 10.2.1 Batched + +![](./diagrams/batch-pipeline.svg) + +```json +{ + "uiv": "0.1.0", + "nnc": "abcdef", + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "run": { + "update-dns": { + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "inputs": { "value": "hello world" } + }, + "notify-bob": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": { "promise/ok": ["/", "update-dns"] } + } + }, + "notify-carol": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": "carol@example.com", + "subject": "Hey Carol, DNSLink was updated!", + "body": { "promise/ok": ["/", "update-dns"] } + } + }, + "log-as-done": { + "with": "https://example.com/report", + "do": "crud/update", + "inputs": { + "payload": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification" + }, + "_": [ + { "promise/ok": ["/", "notify-bob"] }, + { "promise/ok": ["/", "notify-carol"] } + ] + } + } + }, + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } +} +``` + +### 10.2 Serial Pipeline + +![](./diagrams/serial-pipeline.svg) + +```json +{ + "uiv": "0.1.0", + "nnc": "abcdef", + "prf": [ + { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" } + ], + "run": { + "update-dns": { + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "inputs": { "value": "hello world" } + } + }, + "sig": { + "/": { + "bytes": "kQHtTruysx4S8SrvSjTwr6ttTLzc7dd7atANUYT-SRK6v_SX8bjHegWoDak2x6vTAZ6CcVKBt6JGpgnjABpsoL" + } + } +} +``` + +```json +{ + "uiv": "0.1.0", + "nnc": "12345", + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "run": { + "notify-carol": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": "carol@example.com", + "subject": "Hey Carol, DNSLink was updated!", + "body": { + "promise/ok": [ + { + "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" + }, + "update-dns" + ] + } + } + } + }, + "sig": { + "/": { + "bytes": "XZRSmp5cHaXX6xWzSTxQqC95kQHtTruysx4S8SrvSjTwr6ttTLzc7dd7atANUQJXoWThUiVuCHWdMnQNQJgiJi" + } + } +} +``` + +```json +{ + "uiv": "0.1.0", + "nnc": "02468", + "prf": [ + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "run": { + "notify-bob": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "inputs": { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": { + "promise/ok": [ + { + "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" + }, + "update-dns" + ] + } + } + }, + "log-as-done": { + "with": "https://example.com/report", + "do": "crud/update", + "inputs": { + "payload": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification" + }, + "_": [ + { "promise/ok": ["/", "notify-bob"] }, + { + "promise/ok": [ + { + "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" + }, + "notify-carol" + ] + } + ] + } + } + }, + "sig": { + "/": { + "bytes": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } +} +``` + +# 11 Prior Art + +[ucanto RPC](https://github.com/web3-storage/ucanto) from DAG House is a production system that uses UCAN as the basis for an RPC layer. + +The [Capability Transport Protocol (CapTP)](http://erights.org/elib/distrib/captp/index.html) is one of the most influential object-capability systems, and forms the basis for much of the rest of the items on this list. + +The [Object Capability Network (OCapN)](https://github.com/ocapn/ocapn) protocol extends CapTP with a generalized networking layer. It has implementations from the [Spritely Institute](https://www.spritely.institute/) and [Agoric](https://agoric.com/). At time of writing, it is in the process of being standardized. + +[Electronic Rights Transfer Protocol (ERTP)](https://docs.agoric.com/guides/ertp/) builds on top of CapTP for blockchain & digital asset use cases. + +[Cap 'n Proto RPC](https://capnproto.org/) is an influential RPC framework [based on concepts from CapTP](https://capnproto.org/rpc.html#specification). + +# 12 Acknowledgements + +Many thanks to [Mark Miller](https://github.com/erights) for his [pioneering work](http://erights.org/talks/thesis/markm-thesis.pdf) on [capability systems](http://erights.org/). + +Many thanks to [Luke Marsen](https://github.com/lukemarsden) and [Simon Worthington](https://github.com/simonwo) for their feedback on invocation model from their work on [Bacalhau](https://www.bacalhau.org/) and [IPVM](https://github.com/ipvm-wg). + +Many thanks to [Zeeshan Lakhani](https://github.com/zeeshanlakhani) for his many suggestions, references, clarifications, and suggestions on how to restructure sections for clarity. + +Thanks to [Marc-Antoine Parent](https://github.com/maparent) for his discussions of the distinction between declarations and directives both in and out of a UCAN context. + +Many thanks to [Quinn Wilton](https://github.com/QuinnWilton) for her discussion of speech acts, the dangers of signing canonicalized data, and ergonomics. + +Thanks to [Blaine Cook](https://github.com/blaine) for sharing their experiences with OAuth 1, irreversible design decisions, and advocating for keeping the spec simple-but-evolvable. + +Thanks to [Philipp Krüger](https://github.com/matheus23/) for the enthusiastic feedback on the overall design and encoding. + +Thanks to [Christine Lemmer-Webber](https://github.com/cwebber) for the many conversations about capability systems and the programming models that they enable. + +Thanks to [Rod Vagg](https://github.com/rvagg/) for the clarifications on IPLD Schema implicits and the general IPLD worldview. From 28d6cd86fc4312c160f1db989abd425584964768 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 02:14:21 -0800 Subject: [PATCH 02/16] feat: invocation spec --- invocation.md | 1315 ++++++++++++++++++++++++++++--------------------- 1 file changed, 741 insertions(+), 574 deletions(-) diff --git a/invocation.md b/invocation.md index b59329f..327a61e 100644 --- a/invocation.md +++ b/invocation.md @@ -1,7 +1,8 @@ -# UCAN Invocation Specification v0.1.0 +# UCAN Execution Specification v0.1.0 ## Editors +- [Irakli Gozalishvili](https://github.com/Gozala), [DAG House](https://dag.house/) - [Brooklyn Zelenka](https://github.com/expede/), [Fission](https://fission.codes/) ## Authors @@ -11,14 +12,16 @@ ## Depends On -- [DAG-CBOR](https://ipld.io/specs/codecs/dag-cbor/spec/) -- [UCAN](https://github.com/ucan-wg/spec/) -- [UCAN-IPLD](https://github.com/ucan-wg/ucan-ipld/) -- [Varsig](https://github.com/ChainAgnostic/varsig/) +- [DAG-CBOR] +- [UCAN] +- [UCAN-IPLD] +- [Varsig] # 0 Abstract -UCAN Invocation defines a format for expressing the intention to run delegated capabilities from a UCAN, the attested receipts from an execution, and how to extend computation via promise pipelining. +UCAN Execution defines a format for expressing the intention to execute delegated UCAN capabilities, the attested receipts from an execution, and how to extend computation via promise pipelining. + +> This is based on [UCAN invocation] specification, altering it slightly in order to make tasks self-contained as discussed in [#6](https://github.com/ucan-wg/invocation/issues/6) ## Language @@ -62,11 +65,19 @@ Delegating a capability is like the statement `message`. Task is akin to `messag However, there is clearly a distinction between passing a function and invoking it. The same is true for capabilities: delegating the authority to do something is not the same as asking for it to be done immediately, even if sometimes it's clear from context. -## 1.2 Separation of Concerns +## 1.2 Gossiping of delegations + +In web3.storage user `alice@web.mail` can delegate to store file in her space to `bob@send.io` by sending that delegation to `web3.storage`. If service were to interpret this as invocation it would fail due to principal misalignment. By distinguishing capability invocation from delegation service is able to more correctly handle such a message, if it is an invocation it will still error due to principal misalignment, if it is a delegation it will hold it in Bob's inbox to be picked up when he's comes online. + +## 1.3 Separation of Concerns Information about the scheduling, order, and pipelining of tasks is orthogonal to the flow of authority. An agent collaborating with the original executor does not need to know that their call is 3 invocations deep; they only need to know that they been asked to perform some task by the latest invoker. -As we shall see in the [discussion of promise pipelining](#6-promise-pipelining), asking an agent to perform a sequence of tasks before you know the exact parameters requires delegating capabilities for all possible steps in the pipeline. Pulling pipelining detail out of the core UCAN spec serves two functions: it keeps the UCAN spec focused on the flow of authority, and makes salient the level of de facto authority that the executor has (since they can claim any value as having returned for any step). +As we shall see in the [discussion of promise pipelining][pipelines], asking an agent to perform a sequence of tasks before you know the exact parameters requires delegating capabilities for all possible steps in the pipeline. Pulling pipelining detail out of the core UCAN spec serves two functions: it keeps the UCAN spec focused on the flow of authority, and makes salient the level of de facto authority that the executor has (since they can claim any value as having returned for any step). + +``` + +``` ``` ────────────────────────────────────────────Time──────────────────────────────────────────────────────► @@ -113,7 +124,7 @@ As we shall see in the [discussion of promise pipelining](#6-promise-pipelining) ## 1.3 A Note On Serialization -The JSON examples below are given in [DAG-JSON](https://ipld.io/docs/codecs/known/dag-json/), but UCAN Task is actually defined as IPLD. This makes UCAN Task agnostic to encoding. DAG-JSON follows particular conventions around wrapping CIDs and binary data in tags like so: +The JSON examples below are given in [DAG-JSON], but UCAN Task is actually defined as IPLD. This makes UCAN Task agnostic to encoding. DAG-JSON follows particular conventions around wrapping CIDs and binary data in tags like so: ```json // CID @@ -123,11 +134,11 @@ The JSON examples below are given in [DAG-JSON](https://ipld.io/docs/codecs/know {"/": {"bytes": "s0m3Byte5"}} ``` -This format help disambiguate type information in generic DAG-JSON tooling. However, your presentation need not be in this specific format, as long as it can be converted to and from this cleanly. As it is used for the signature format, DAG-CBOR is RECOMMENDED. +This format help disambiguate type information in generic [DAG-JSON] tooling. However, your presentation need not be in this specific format, as long as it can be converted to and from this cleanly. As it is used for the signature format, [DAG-CBOR] is RECOMMENDED. ## 1.4 Signatures -All payloads described in this spec MUST be signed with a [Varsig](https://github.com/ChainAgnostic/varsig/). +All payloads described in this spec MUST be signed with a [Varsig]. # 2 High-Level Concepts @@ -154,143 +165,184 @@ The executor MUST be the UCAN delegate. Their DID MUST be set the in `aud` field ## 2.2 Components -![](./diagrams/concepts.svg) +```mermaid +flowchart LR + U(UCAN) -- Auth --> I + I(Invocation) -- Output --> O(Result) + O -- Consume --> P(Promise) + O -- Attest --> R(Receipt) + R -- Short-circuit --> M(Memoization) +``` -### 2.2.1 Closure +### 2.2.1 Invocation -A [Closure](#3-closure) is like a deferred function application: a request to perform some action on a resource with specific inputs. +An [Invocation] is like a cryptographically signed function application, a request to perform some action on a resource with specific input. Invocation MAY have optional metadata that is not used to describe the meaning of the computation or effect to be run. Executor MUST produce same result regardless of the metadata. -### 2.2.2 Task +### 2.2.2 Receipt -A [Task](#4-task) extends a Closure with additional metadata that is not used to describe the meaning of the computation or effect to be run. +A [Receipt] describes the output of an invocation. It is referenced either by the invocation itself or an IPLD Link of the invocation. -### 2.2.3 Batch +### 2.2.3 Promise -A [Batch](#5-batch) is a way of requesting more than one action at once. +A [promise] is a reference to expected result of the invocation receipt. -### 2.2.4 Invocation +## 2.3 IPLD Schema -An [Invocation](#6-invocation) is the cryptographically signed container for a Batch. This is the step that "forces" the "deferred" Closure. +> ℹ️ We use extended [IPLD Schema] notation with generics. Standard IPLD Schema can be derived by ignoring parameters enclosed in angle brackets and interpreting them `Any`. Example below illustrates same type first in extended syntax and then same type in standard syntax +> +> ```ipldsch +> type Box struct { +> value T +> } +> ``` +> +> Is equivalent of +> +> ```ipldsch +> type Box struct { +> value Any +> } +> ``` -### 2.2.5 Pointers +```ipldsch +type Invocation struct { + v SemVer -An [Invocation Pointer](#7-pointer) identifies a specific invocation. An Invoked Task Pointer points to a unique Task inside an Invocation. + iss Principal + aud Principal -### 2.2.6 Result + with URI + do Ability + input In (implicit {}) -A [Result](#8-result) is the output of a Closure. + meta {String : Any} (implicit {}) -### 2.2.7 Receipt + prf [&UCAN] + nnc optional String + nbf optional Int -A [Receipt](#9-receipt) describes the output of an invocation, referenced by its Invocation Pointer. + s Varsig +} -### 2.2.8 Promise +type Receipt struct { + src &Invocation -A [promise](#10-promise) is a reference to the receipt of an action that has yet to return a receipt. + # output of the invocation + out Out -## 2.3 IPLD Schema + # Related receipts + origin optional &Receipt -```ipldsch -type Closure struct { - with URI - do Ability - inputs Any -} + # All the other metadata + meta { String: Any } -type Task struct { - with URI - do Ability - inputs Any - meta {String : Any} (implicit {}) -} + # Principal that issued this receipt + iss Principal -type Batch union { - | Named {String : Task} - | List [Task] -} + # Signature from the `iss`. + s Varsig -type Invocation struct { - uiv SemVer - run Batch - prf [&UCAN] - nnc String - meta {String : Any} (implicit {}) - sig Varsig + # Proof that `iss` was authorized to by invocation `aud` to issue + # receipts. Can be omitted if `src.aud === this.iss` + prf [&UCAN] implicit ([]) } -type InvocationPointer union { - | "/" -- Relative to the current invocation - | &Invocation +# Promise is a way to reference data from the output of the invocation +type Promise union { + Await "promise/await" } -type InvokedTaskPointer struct { - envl InvocationPointer - label String +type Await struct { + # promise to select from + invocation InvocationReference + # `/` delimited path from the `out` of the `Receipt`. + # If omitted implies whole `out` field. + selector optional String } representation tuple -type Receipt struct { - ran &InvocationPointer - out {String : Result} - rec {String : &Receipt} - meta {String : Any} - sig Varsig -} - -type Result union { - | Any ("ok") -- Success - | Any ("err") -- Failure -} representation keyed - -type Promise union { - | InvokedTaskPointer "promise/ok" - | InvokedTaskPointer "promise/err" - | InvokedTaskPointer "promise/*" -} representation keyed +type InvocationReference union { + | Invocation + | &Invocation +} representation kinded ``` -# 3 Closure +# 3 Invocation -A Closure is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, inputs)` triple. The `inputs` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. +An invocation is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, input)` triple. The `input` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. -Using the JavaScript analogy from the introduction, a Closure is similar to wrapping a call in an anonymous function: +Using the JavaScript analogy from the introduction, an invocation is similar to function application: ```json { - "with": "mailto://alice@example.com", + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + + "with": "mailto:alice@example.com", "do": "msg/send", "inputs": { "to": ["bob@example.com", "carol@example.com"], "subject": "hello", "body": "world" + }, + + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } } } ``` ```js // Pseudocode -;() => - msg.send("mailto:alice@example.com", { - to: ["bob@example.com", "carol@example.com"], - subject: "hello", - body: "world", - }) +msg.send("mailto:alice@example.com", { + to: ["bob@example.com", "carol@example.com"], + subject: "hello", + body: "world", +}) ``` -Later, when we explore [Promises](# 10-promise), this also includes capturing the promise: +Later, when we explore [Promise]s, this also includes capturing the promise: ```json { - "mailingList": { - "with": "https://example.com/mailinglist", - "do": "crud/read" + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": { + "promise/await": [ + { + "v": "0.1.0", + "iss": "did:key:zAlice", + "aud": "did:web:ucan.run", + "with": "https://example.com/mailinglist", + "do": "crud/read", + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [{"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"}] + }, + "ok" + ], + } + "subject": "hello", + "body": "world" }, - "sendEmail": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": { - "to": {"promise/*": ["/", "get-mailing-list"]} - "subject": "hello", - "body": "world" + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" } } } @@ -298,48 +350,103 @@ Later, when we explore [Promises](# 10-promise), this also includes capturing th ```js // Pseudocode -const mailingList = crud.read("https://exmaple.com/mailinglist", {}) // ---┐ -// │ -const sendEmail = () => - msg.send("mailto:alice@example.com", { - // │ - to: mailingList, // <----------------------------------------------------┘ - subject: "hello", - body: "world", - }) +const mailingList = crud.read("https://exmaple.com/mailinglist", {}) +msg.send("mailto:alice@example.com", { + to: mailingList.catch(error => error).then(result => result.ok), + subject: "hello", + body: "world", +}) ``` -## 3.1 Fields +## 3.1 Schema ```ipldsch -type Closure struct { +type Invocation struct { + v SemVer + + iss Principal + aud Principal + with URI do Ability - inputs Any + input In (implicit {}) + + meta {String : Any} (implicit {}) + + prf [&UCAN] + nnc optional String + nbf optional Int + + s Varsig } ``` -### 3.1.1 Resource +## 3.2 Fields + +An Invocation authorizes execution of the capability. There are a few invariants that MUST hold between the `iss`, `aud`, `prf` and `sig` fields: + +- The `iss` MUST be the Invoker. +- All of the `prf` UCANs MUST list the Invoker in their `iss`. +- All of the `prf` UCANs MUST list the Executor in their `aud` field, grating it authority to perform some action on a resource, or be the root authority for it. +- The `sig` field MUST be produced by the Invoker +- Invocation MUST be provably authorized by the UCANs in the `prf` field. + +### 3.2.1 UCAN Invocation Version + +The `v` field MUST contain the SemVer-formatted version of the UCAN Invocation Specification that this struct conforms to. + +### 3.2.2 Invoker + +The `iss` field MUST be a principal authorizing the invocation. It MUST be encoded in format describe in [UCAN-IPLD]. + +### 3.2.3 Executor + +The `aud` field MUST be a principal authorized to perform the invocation. It MUST be encoded in format described in [UCAN-IPLD]. + +### 3.2.4 Resource The `with` field MUST contain the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the resource being accessed. If the resource being accessed is some static data, it is RECOMMENDED to reference it by the [`data`](https://en.wikipedia.org/wiki/Data_URI_scheme), [`ipfs`](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls), or [`magnet`](https://en.wikipedia.org/wiki/Magnet_URI_scheme) URI schemes. -### 3.1.2 Ability +### 3.2.5 Ability The `do` field MUST contain a [UCAN Ability](https://github.com/ucan-wg/spec/#23-ability). This field can be thought of as the message or trait being sent to the resource. -### 3.1.3 Inputs +### 3.2.6 Input + +The `input` field MUST contain any arguments expected by the URI/Ability pair. This MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. + +UCAN capability provided in proofs MAY impose certain constraint on the type of `Input` allowed. + +### 3.2.7 Proofs -The `inputs` field MUST contain any arguments expected by the URI/Ability pair. This MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. +The `prf` field MUST contain links to any UCANs that provide the authority to perform the invocation. All of the outermost `aud` fields MUST be set to the [Executor]'s DID. All of the outermost `iss` field MUST be set to the [Invoker]'s DID. -### 3.2 DAG-JSON Examples +### 3.2.8 Nonce + +If present, the OPTIONAL `nnc` field MAY include a random nonce expressed in ASCII. This field can ensures that multiple invocations are unique. + +### 3.2.9 Metadata + +The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. + +Data inside the `meta` field SHOULD NOT be used for [Receipt]s. + +### 3.2.10 Signature + +The `s` field MUST contain a [Varsig] of the invocation payload, an invocation without `meta` and `s` fields encoded in [DAG-CBOR]. + +## 3.3 DAG-JSON Examples Interacting with an HTTP API: ```json { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", "with": "https://example.com/blog/posts", "do": "crud/create", - "inputs": { + "input": { "headers": { "content-type": "application/json" }, @@ -349,7 +456,15 @@ Interacting with an HTTP API: "topics": ["authz", "journal"], "draft": true } - } + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ] } ``` @@ -357,13 +472,28 @@ Sending Email: ```json { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", "with": "mailto:akiko@example.com", "do": "msg/send", - "inputs": { + "input": { "to": ["boris@example.com", "carol@example.com"], "subject": "Coffee", "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - } + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ] } ``` @@ -371,279 +501,300 @@ Running WebAssembly from binary: ```json { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", "do": "wasm/run", - "inputs": { + "input": { "func": "add_one", "args": [42] - } + }, + "meta": { + "dev/notes": "The standard Wasm demo", + "ipvm/verification": "attestation", + "ipvm/resources": { + "gas": 5000 + } + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ] } ``` -Executing all of the capabilities in a UCAN directly: +Batch invocation is simply passing promises as inputs to an invocation ```json { - "with": "ipfs://bafkreiemaanh3kxqchhcdx3yckeb3xvmboztptlgtmnu5jp63bvymxtlva", - "do": "ucan/run", - "inputs": null + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "did:web:ucan.run", + "do": "control/batch", + "input": { + "publishPost": { + "promise/await": [{ + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + {"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"} + ] + }, + "ok" + ], + "sendEmail": { + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "mailto:akiko@example.com", + "do": "msg/send", + "input": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + {"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"} + ] + }, + "sendTextMessage": { + "/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle" + } + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, } ``` -# 4 Task - -A Task is subtype of a [Closure](#3-closure), adding an OPTIONAL metadata field. If not present, the `meta` field defaults to an empty map. A Task can be trivially converted to a Closure by removing the `meta` field. - -```ipldsch -type Task struct { - with URI - do Ability - inputs Any - meta {String : Any} (implicit {}) +```json +{ + "v": "0.1.0", + "nnc": "6c*97-3=", + "run": { + "left": { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + } + }, + "right": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "inputs": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "meta": { + "dev/tags": ["friends", "coffee"], + "dev/priority": "high" + } + } + }, + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, + { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + ], + "meta": { + "notes/personal": "I felt like making an invocation today!", + "ipvm/config": { + "time": [5, "minutes"], + "gas": 3000 + } + }, + "sig": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } } ``` -## 4.1 Fields +# 4 Receipt -### 4.1.1 Closure Fields +An Invocation Receipt is an attestation of the Result of an Invocation. A Receipt MUST be signed by the Executor (the `aud` of the associated UCANs) or it's delegate, in which case proof of delegation (of the invoked capability) from Executor to the Signer (the `iss` of the receipt) MUST be provided in `prf`. -The `with`, `do`, and `inputs` field from [Closure](#3-closure) remain unchanged. +**NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. -### 4.1.2 Metadata +Receipts MUST use the same version as the invocation that they contain. -The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. +## 4.1 Schema -## 4.2 DAG-JSON Examples +```ipldsch +type Receipt struct { + src &Invocation -Sending Email: + # output of the invocation + out Out -```json -{ - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" - } -} -``` + # Related receipts + origin optional &Receipt -Running WebAssembly from binary: + # All the other metadata + meta { String: Any } -```json -{ - "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", - "do": "wasm/run", - "inputs": { - "func": "add_one", - "args": [42] - }, - "meta": { - "dev/notes": "The standard Wasm demo", - "ipvm/verification": "attestation", - "ipvm/resources": { - "gas": 5000 - } - } + # Principal that issued this receipt + iss Principal + + # Proof that `iss` was authorized to by invocation `aud` to issue + # receipts. Can be omitted if `src.aud === this.iss` + prf [&UCAN] implicit ([]) + + # Signature from the `iss`. + s Varsig } ``` -# 5 Batch +## 4.2 Fields -A Batch is a collection of Tasks, either as a `List` (array) or `Named` (map). In many situations, sending multiple requests in a Batch is more efficient than one-at-a-time. +### 4.2.1 Invocation -A `List` is sugar for a `Named` map, where the keys are the array index number as strings. +The `src` field MUST include a link to the Invocation that the Receipt is for. -```ipldsch -type Batch union { - | Named {String : Task} - | List [Task] -} -``` +### 4.2.2 Output -## 5.1 Fields +The `out` field MUST contain the output of the invocation. It is RECOMMENDED that invocation produce output in [Result] format or it's extension with added type variants when needed, which allows selecting either success or failure branch during promise pipelining. -Each Task in a Batch contains MAY be referenced by a string label. +### 4.2.2.1 Result -## 5.2 DAG-JSON Examples +A Result records success or a failure state of the [Invocation]. -### 5.2.1 Named +#### 4.2.2.1.1 Variants -```json -{ - "left": { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - "right": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" - } - } -} +```ipldsch +type Result union { + | T ("ok") # Success + | X ("error") # Failure +} representation keyed ``` -### 5.2.2 List +#### 4.2.2.1.2 Success + +The success branch MUST contain the value returned from a successful invocation wrapped in the `"ok"` tag. The exact shape of the returned data is left undefined to allow for flexibility. ```json -[ - { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" - } - } -] +{ "ok": 42 } ``` -# 6 Invocation +#### 4.2.2.1.3 Failure -As [noted in the introduction](#112-lazy-vs-eager-evaluation), there is a difference between a reference to a function and calling that function. [Closures](#3-closure) and [Tasks](#4-task) are not executable until they have been provided provable authority from the [Invoker](#211-invoker) (in the form of UCANs), and signed with the Invoker's private key. +The failure branch MAY contain detail about why execution failed wrapped in the `"error"` tag. The exact shape of the returned data is left undefined to allow for flexibility. -## 6.1 IPLD Schema +If no information is available, this field SHOULD be set to `{}`. -```ipldsch -type Invocation struct { - uiv SemVer - run Batch - prf [&UCAN] - nnc String - meta {String : Any} (implicit {}) - sig Varsig +```json +{ + "error": { + "dev/reason": "unauthorized", + "http/status": 401 + } } ``` -## 6.2 Fields +### 4.2.2.2 Extended Result -An Invocation authorizes one or more Tasks to be run. There are a few invariants that MUST hold between the `run`, `prf` and `sig` fields: +An extension of Result records SHOULD be used when result of [Invocation] does not fit binary success / failure criteria. -- All of the `prf` UCANs MUST list the Invoker in their `iss` -- The `sig` field MUST be produced by the Invoker -- All of the `run` Tasks MUST be provably authorized by the UCANs in the `prf` field -- The Executor(s) MUST be listed in the `aud` field of a UCAN that grants it the authority to perform some action on a resource, or be the root authority for it - -### 6.2.1 UCAN Task Version - -The `uiv` field MUST contain the SemVer-formatted version of the UCAN Task Specification that this struct conforms to. +```ipldsch +type Status union { + | T ("ok") # Success + | X ("error") # Failure + | P ("pending") # Pending +} representation keyed +``` -### 6.2.2 Task +```json +{ "pending": {} } +``` -The `run` field MUST contain a link to the [Task](#31-single-invocation) itself. +### 4.2.3 Recursive Receipt -### 6.2.3 Proofs +In the case when Invocation execution is delimited it MAY produce multiple states that SHOULD be chained by `origin` field. -The `prf` field MUST contain links to any UCANs that provide the authority to run the actions. All of the outermost `aud` fields MUST be set to the [Executor](#212-executor)'s DID. All of the outermost `iss` field MUST be set to the [Invoker](#211-invoker)'s DID. +### 4.2.4 Metadata Fields -### 6.2.4 Nonce +The metadata field MAY be omitted or used to contain additional data about the receipt. This field MAY be used for tags, commentary, trace information, and so on. -The `nnc` field MUST include a random nonce field expressed in ASCII. This field ensures that multiple invocations are unique. +### 4.2.5 Receipt Issuer -### 6.2.5 Metadata +The `iss` field MUST contain the signer of the receipt. It MAY be an `aud` of the Invocation or it's delegate. In later case proof delegating invoked capability MUST be provided. -If present, the OPTIONAL `meta` map MAY contain free form fields. This provides a place for extension of the invocation type. +### 4.2.6 Proofs -Data inside the `meta` field SHOULD NOT be used for [Receipts](#9-receipt). +The `prf` field MUST contain links to any UCANs that delegate authority to perform the invocation from the Executor to the receipt issuer (`iss`). If Executor and the receipt issuer are same no proofs are required. -### 6.2.6 Signature +### 4.2.7 Signature -The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `prf`, and `nnc` fields. +The `s` field MUST contain a [Varsig] of the receipt payload, a receipt without `s` fields encoded in [DAG-CBOR]. The signature MUST be generated by the `iss`. -## 6.3 DAG-JSON Examples +## 4.3 DAG-JSON Examples ```json { - "uiv": "0.1.0", - "nnc": "6c*97-3=", - "run": { - "left": { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - "right": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + "src": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, + "out": { + "ok": [ + { + "from": "bob@example.com", + "text": "Hello world!" }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" + { + "from": "carol@example.com", + "text": "What's up?" } - } + ] + }, + "meta": { + "time": [400, "hours"], + "retries": 2 }, + "iss": "did:web:ucan.run", "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } ], - "meta": { - "notes/personal": "I felt like making an invocation today!", - "ipvm/config": { - "time": [5, "minutes"], - "gas": 3000 - } - }, "sig": { "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt_VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" } } } @@ -651,7 +802,7 @@ The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) # 7 Pointer -An Invocation Pointer references a specific [Invocation](#6-invocation), either directly by CID (absolute), or from inside the Invocation itself (relative). +An Invocation Pointer references a specific [Invocation], either directly by CID (absolute), or from inside the Invocation itself (relative). ```ipldsch type InvocationPointer union { @@ -942,158 +1093,141 @@ Will select the marked field in this List invocation: } ``` -# 8 Result - -A Result records the output of a [Task](#4-task), as well as its success or failure state. - -## 8.1 Variants - -```ipldsch -type Result union { - | Any ("ok") -- Success - | {String: Any} ("err") -- Failure -} representation keyed -``` - -### 8.1.1 Success +# 5 Promise -The success branch MUST contain the value returned from a successful Task wrapped in the `"ok"` tag. The exact shape of the returned data is left undefined to allow for flexibility in various Task types. +> Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips +> +> — [Mark Miller](https://github.com/erights), [Robust Composition](http://www.erights.org/talks/thesis/markm-thesis.pdf) -```json -{ "ok": 42 } -``` +There MAY not be enough information to described an Invocation at creation time. However, all of the information required to construct the next request in a sequence MAY be available in the same Batch, or in a previous (but not yet complete) Invocation. -### 8.1.2 Failure +Some invocations MAY require input from set of other invocations. Waiting for each request to complete before proceeding to the next task has a performance impact due to the amount of latency. [Promise pipelining](http://erights.org/elib/distrib/pipeline.html) is a solution to this problem: by referencing a prior invocation, a pipelined invocation can direct the executor to use the output of one invocations into the input of the other. This liberates the invoker from waiting for each step. -The failure branch MAY contain detail about why execution failed wrapped in the `"err"` tag. It is left undefined in this specification to allow for Task types to standardize the data that makes sense in their contexts. +A Promise MAY be used as a variable placeholder for a concrete value in an [Invocation] output, waiting on a previous step to complete. -If no information is available, this field SHOULD be set to `{}`. +For example, consider the following invocation batch: ```json { - "err": { - "dev/reason": "unauthorized", - "http/status": 401 - } -} -``` - -# 9 Receipt + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", -An Invocation Receipt is an attestation of the Result of an Invocation. A Receipt MUST be signed by the Executor (the `aud` of the associated UCANs). - -**NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. - -Receipts MUST use the same version as the invocation that they contain. - -## 9.1 Fields - -```ipldsch -type Receipt struct { - ran &InvokedTaskPointer - out {String : Result} - rec {String : &Receipt} - meta {String : Any} - sig Varsig -} -``` - -### 9.1.1 Task - -The `inv` field MUST include a link to the Task that the Receipt is for. - -### 9.1.2 Output - -The `out` field MUST contain the output of steps of the call graph, indexed by the task name inside the invocation. The `out` field MAY omit any tasks that have not yet completed, or results which are not public. An `Task` may be associated to zero or more `Receipts`. - -A `Result` MAY include recursive `Receipt` CIDs in on the `Success` branch. As a Task may require subdelegation, the OPTIONAL `rec` field MAY be used to include recursive `Receipt`s. - -### 9.1.3 Recursive Receipt - -In the case that an Invocation was subdelegated to another Executor and the Result bubbled up, a recursive Receipt SHOULD be included in the `rec` field. - -### 9.1.4 Metadata Fields - -The metadata field MAY be omitted or used to contain additional data about the receipt. This field MAY be used for tags, commentary, trace information, and so on. - -### 9.1.5 Signature - -The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `out`, and `meta` fields. The signature MUST be generated by the Executor, which means the public key in the `aud` field of the UCANs backing the Task. + "with": "mailto:akiko@example.com", + "do": "msg/send", -## 9.2 DAG-JSON Examples + "inputs": { + "to": { + "promise/await": [ + { + "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" + }, + "ok" + ] + }, + "subject": "Coffee", + "body": { + "promise/ok": [ + { + "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" + }, + "ok" + ] + } + } -```json -{ - "ran": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, - "out": { - "ok": [ - { - "from": "bob@example.com", - "text": "Hello world!" - }, - { - "from": "carol@example.com", - "text": "What's up?" - } - ] - }, - "meta": { - "time": [400, "hours"], - "retries": 2 - }, - "sig": { + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + ], + "s": { "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt_VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" } } } ``` -# 10 Promise - -> Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips -> -> — [Mark Miller](https://github.com/erights), [Robust Composition](http://www.erights.org/talks/thesis/markm-thesis.pdf) - -There MAY not be enough information to described an Invocation at creation time. However, all of the information required to construct the next request in a sequence MAY be available in the same Batch, or in a previous (but not yet complete) Invocation. Waiting for each request to complete before proceeding to the next task has a performance impact due to the amount of latency. [Promise pipelining](http://erights.org/elib/distrib/pipeline.html) is a solution to this problem: by referencing a prior invocation, a pipelined invocation can direct the executor to use the output of earlier invocations into the input of a later one. This liberates the invoker from waiting for each step. - -A Promise is a placeholder value MAY be used as a variable placeholder for a concrete value in a [Closure](#3-closure), waiting on a previous step to complete. - -One way of seeing the names in a [`Batch`](#5-batch) is as variables for the return of each Closure. These can now be referenced by other Closures. +Which is roughly equivalent of the of the following invocation, which inlines above linked invocations instead for illustration purposes. -For example, consider the following batch: +> ℹ️ In most cases invocation batches are likely to be [CAR] encoded to avoid running into [block size limitation](https://discuss.ipfs.tech/t/git-on-ipfs-links-and-references/730/2) making inlined variants less common. ```json { - "run": { - "create-draft": { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything..." - } - } - }, - "get-editors": { - "with": "https://example.com/users/editors", - "do": "crud/read" + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + + "with": "mailto:akiko@example.com", + "do": "msg/send", + + "inputs": { + "to": { + "promise/await": [ + { + "meta": { "name": "create-draf" }, + + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "inputs": { + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything..." + } + }, + "prf": [ + "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } + }, + "ok" + ] }, - "notify": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": { "promise/ok": ["/", "get-editors"] }, - "subject": "Coffee", - "body": { "promise/ok": ["/", "create-draft"] } - } + "subject": "Coffee", + "body": { + "promise/ok": [ + { + "meta": { "name": "get-editors" }, + + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + + "with": "https://example.com/users/editors", + "do": "crud/read" + + "prf": [ + "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } + }, + "ok" + ] + } + }, + + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" } } } ``` -By analogy, this can be interpreted roughly as follows: +By analogy, above examples can be interpreted roughly as follows: ```js const createDraft = crud.create("https://example.com/blog/posts", { @@ -1112,39 +1246,168 @@ const notify = msg.send("mailto:akiko@example.com", { }) ``` -While a Promise MAY be substituted for any field in a Closure, substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. +While a Promise MAY be substituted for any field in an Invocation, substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. + +After resolution, the Invocation MUST be validated against the UCANs known to the Executor. A Promise resolved to an invocation that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. -After resolution, the Task MUST be validated against the UCANs known to the Executor. A Promise resolved to a Task that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. +Promises MAY be used inside of a single Invocation, or across multiple Invocations, and MAY even be across multiple Invokers and Executors. As long as the pointer can be resolved, any invocation MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). -Promises MAY be used inside of a single Invocation's Closures, or across multiple Invocations, and MAY even be across multiple Invokers. As long as the pointer can be resolved, any invoked Task MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). +A Promise SHOULD resolve to a [Result] or it's [extension][extended result]. If a particular branch's value is required to be unwrapped, the Result tag (`ok` or `error`) MAY be supplied as a selector. -A Promise MUST resolve to a [Result](#8-result). If a particular branch's value is required to be unwrapped, the Result tag (`ok` or `err`) MAY be supplied. +An invocation MUST fail if promise is resolved does not match the supplied branch selector. -## 10.1 Enum & Fields +## 5.1 Schema -The following describe a pointer to the eventual value in a Promise, on either branch (`promise/*`), or specifically the success (`promise/ok`) or failure (`promise/err`) branches. +The `Promise` describes a pointer to the eventual value in a Promise. When the `selector` of the `Await` omitted pointer resolves to the invocation result (`out` of the Receipt), otherwise it resolves to the specified branch `ok` on success and `error` on failure. Selector could also be used to resolve value nested deeper in any branch. ```ipldsch type Promise union { - | InvokedTaskPointer "promise/*" - | InvokedTaskPointer "promise/ok" - | InvokedTaskPointer "promise/err" -} representation keyed + Await "promise/await" +} + +type Await struct { + # promise to select from + invocation InvocationReference + # `/` delimited path from the `out` of the `Receipt`. + # If omitted implies whole `out` field. + selector optional String +} representation tuple + +type InvocationReference union { + | Invocation + | &Invocation +} representation kinded ``` If there are dependencies or ordering required, then you need a promise pipeline -## 10.2 Pipelines +## 5.2 Pipelines Pipelining uses promises as inputs to determine the required dataflow graph. The following examples both express the following dataflow graph: -### 10.2.1 Batched +### 5.2.1 Batched + +```mermaid +flowchart BR + update-dns("with: dns:example.com?TYPE=TXT + do: crud/update") + notify-bob("with: mailto://alice@example.com + do: msg/send + to: bob@example.com") + notify-carol("with: mailto://alice@example.com + do: msg/send + to: carol@example.com") + + log-as-done("with: https://example.com/report + do: crud/update") -![](./diagrams/batch-pipeline.svg) + update-dns --> notify-bob --> log-as-done + update-dns --> notify-carol --> log-as-done +``` ```json { - "uiv": "0.1.0", + "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce": { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "input": { "value": "hello world" }, + "prf": [{"/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u"}], + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } + }, + "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4": { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": { + "promise/await": [ + { + "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" + }, + "ok" + ] + } + }, + "prf": [{ "/": "bafyreialservj7dxazg4cskm5fuqwh5atgs54rgkvpmtkylbddygs37tce" }], + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } + }, + "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u": { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": "carol@example.com", + "subject": "Hey Carol, DNSLink was updated!", + "body": { + "promise/await": [ + { + "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" + }, + "ok" + ] + } + }, + "prf": [{ "/": "bafyreifusp3qabhrzexltt6ausy4cz7t3cjxcnmwyiqkon5iuthx4h5uo4" }], + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } + }, + "bafyreigr4evtgj6zojwhvceurdbpclm2wrftka5snymvze53fcpbtmiaeq": { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "https://example.com/report", + "do": "crud/update", + "input": { + "payload": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification" + }, + "_": [ + { + "promise/await": [ + { "/": "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4" }, + "ok" + ] + }, + { + "promise/await": [ + { "/": "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u" }, + "ok" + ] + } + ] + }, + "prf": [{ "/": "bafyreihwfiwuv4f2sajj7r247rezqaarhydd7ffod4tcesdv2so5nkmq7y" }], + "sig": { + "/": { + "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + } + } + } +} +{ + "v": "0.1.0", "nnc": "abcdef", "prf": [ { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, @@ -1198,121 +1461,7 @@ Pipelining uses promises as inputs to determine the required dataflow graph. The } ``` -### 10.2 Serial Pipeline - -![](./diagrams/serial-pipeline.svg) - -```json -{ - "uiv": "0.1.0", - "nnc": "abcdef", - "prf": [ - { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" } - ], - "run": { - "update-dns": { - "with": "dns:example.com?TYPE=TXT", - "do": "crud/update", - "inputs": { "value": "hello world" } - } - }, - "sig": { - "/": { - "bytes": "kQHtTruysx4S8SrvSjTwr6ttTLzc7dd7atANUYT-SRK6v_SX8bjHegWoDak2x6vTAZ6CcVKBt6JGpgnjABpsoL" - } - } -} -``` - -```json -{ - "uiv": "0.1.0", - "nnc": "12345", - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "run": { - "notify-carol": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": { - "to": "carol@example.com", - "subject": "Hey Carol, DNSLink was updated!", - "body": { - "promise/ok": [ - { - "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" - }, - "update-dns" - ] - } - } - } - }, - "sig": { - "/": { - "bytes": "XZRSmp5cHaXX6xWzSTxQqC95kQHtTruysx4S8SrvSjTwr6ttTLzc7dd7atANUQJXoWThUiVuCHWdMnQNQJgiJi" - } - } -} -``` - -```json -{ - "uiv": "0.1.0", - "nnc": "02468", - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "run": { - "notify-bob": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": { - "to": "bob@example.com", - "subject": "DNSLink for example.com", - "body": { - "promise/ok": [ - { - "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" - }, - "update-dns" - ] - } - } - }, - "log-as-done": { - "with": "https://example.com/report", - "do": "crud/update", - "inputs": { - "payload": { - "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com", "carol@example.com"], - "event": "email-notification" - }, - "_": [ - { "promise/ok": ["/", "notify-bob"] }, - { - "promise/ok": [ - { - "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" - }, - "notify-carol" - ] - } - ] - } - } - }, - "sig": { - "/": { - "bytes": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - -# 11 Prior Art +# 6 Prior Art [ucanto RPC](https://github.com/web3-storage/ucanto) from DAG House is a production system that uses UCAN as the basis for an RPC layer. @@ -1343,3 +1492,21 @@ Thanks to [Philipp Krüger](https://github.com/matheus23/) for the enthusiastic Thanks to [Christine Lemmer-Webber](https://github.com/cwebber) for the many conversations about capability systems and the programming models that they enable. Thanks to [Rod Vagg](https://github.com/rvagg/) for the clarifications on IPLD Schema implicits and the general IPLD worldview. + +[ucan invocation]: https://github.com/ucan-wg/invocation/tree/rough +[dag-json]: https://ipld.io/docs/codecs/known/dag-json/ +[varsig]: https://github.com/ChainAgnostic/varsig/ +[ipld schema]: https://ipld.io/docs/schemas/ +[varsig]: https://github.com/ChainAgnostic/varsig +[ucan-ipld]: https://github.com/ucan-wg/ucan-ipld/ +[ucan]: https://github.com/ucan-wg/spec/ +[dag-cbor]: https://ipld.io/specs/codecs/dag-cbor/spec/ +[car]: https://ipld.io/specs/transport/car/carv1/ +[result]: #4221-Result +[extended result]: #4222-Extended-Result +[pipelines]: #52-Pipelines +[invocation]: #3-Invocation +[receipt]: #4-receipt +[promise]: #5-promise +[executor]: #323-executor +[invoker]: #322-invoker From b8389ee0ba1b5f026aee728c6a0a91fedf33a331 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 02:19:07 -0800 Subject: [PATCH 03/16] chore: embrace erights pipeline syntax --- invocation.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/invocation.md b/invocation.md index 327a61e..775859a 100644 --- a/invocation.md +++ b/invocation.md @@ -249,7 +249,7 @@ type Receipt struct { # Promise is a way to reference data from the output of the invocation type Promise union { - Await "promise/await" + Await "<-" } type Await struct { @@ -317,7 +317,7 @@ Later, when we explore [Promise]s, this also includes capturing the promise: "do": "msg/send", "input": { "to": { - "promise/await": [ + "<-": [ { "v": "0.1.0", "iss": "did:key:zAlice", @@ -539,7 +539,7 @@ Batch invocation is simply passing promises as inputs to an invocation "do": "control/batch", "input": { "publishPost": { - "promise/await": [{ + "<-": [{ "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", "aud": "did:web:ucan.run", "with": "https://example.com/blog/posts", @@ -1118,7 +1118,7 @@ For example, consider the following invocation batch: "inputs": { "to": { - "promise/await": [ + "<-": [ { "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" }, @@ -1162,7 +1162,7 @@ Which is roughly equivalent of the of the following invocation, which inlines ab "inputs": { "to": { - "promise/await": [ + "<-": [ { "meta": { "name": "create-draf" }, @@ -1262,7 +1262,7 @@ The `Promise` describes a pointer to the eventual value in a Promise. When the ` ```ipldsch type Promise union { - Await "promise/await" + Await "<-" } type Await struct { @@ -1331,7 +1331,7 @@ flowchart BR "to": "bob@example.com", "subject": "DNSLink for example.com", "body": { - "promise/await": [ + "<-": [ { "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" }, @@ -1356,7 +1356,7 @@ flowchart BR "to": "carol@example.com", "subject": "Hey Carol, DNSLink was updated!", "body": { - "promise/await": [ + "<-": [ { "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" }, @@ -1385,13 +1385,13 @@ flowchart BR }, "_": [ { - "promise/await": [ + "<-": [ { "/": "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4" }, "ok" ] }, { - "promise/await": [ + "<-": [ { "/": "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u" }, "ok" ] From 88bad3f8bf1b48f9f92b77ef3134b1c522cd0d3c Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 02:25:08 -0800 Subject: [PATCH 04/16] chore: remove obsolete content --- invocation.md | 437 +++----------------------------------------------- 1 file changed, 23 insertions(+), 414 deletions(-) diff --git a/invocation.md b/invocation.md index 775859a..4581894 100644 --- a/invocation.md +++ b/invocation.md @@ -75,10 +75,6 @@ Information about the scheduling, order, and pipelining of tasks is orthogonal t As we shall see in the [discussion of promise pipelining][pipelines], asking an agent to perform a sequence of tasks before you know the exact parameters requires delegating capabilities for all possible steps in the pipeline. Pulling pipelining detail out of the core UCAN spec serves two functions: it keeps the UCAN spec focused on the flow of authority, and makes salient the level of de facto authority that the executor has (since they can claim any value as having returned for any step). -``` - -``` - ``` ────────────────────────────────────────────Time──────────────────────────────────────────────────────► @@ -597,59 +593,6 @@ Batch invocation is simply passing promises as inputs to an invocation } ``` -```json -{ - "v": "0.1.0", - "nnc": "6c*97-3=", - "run": { - "left": { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - "right": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" - } - } - }, - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "meta": { - "notes/personal": "I felt like making an invocation today!", - "ipvm/config": { - "time": [5, "minutes"], - "gas": 3000 - } - }, - "sig": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - # 4 Receipt An Invocation Receipt is an attestation of the Result of an Invocation. A Receipt MUST be signed by the Executor (the `aud` of the associated UCANs) or it's delegate, in which case proof of delegation (of the invoked capability) from Executor to the Signer (the `iss` of the receipt) MUST be provided in `prf`. @@ -703,8 +646,8 @@ A Result records success or a failure state of the [Invocation]. ```ipldsch type Result union { - | T ("ok") # Success - | X ("error") # Failure + | T ("ok") # Success + | X ("error") # Failure } representation keyed ``` @@ -737,9 +680,9 @@ An extension of Result records SHOULD be used when result of [Invocation] does n ```ipldsch type Status union { - | T ("ok") # Success - | X ("error") # Failure - | P ("pending") # Pending + | T ("ok") # Success + | X ("error") # Failure + | P ("pending") # Pending } representation keyed ``` @@ -800,299 +743,6 @@ The `s` field MUST contain a [Varsig] of the receipt payload, a receipt without } ``` -# 7 Pointer - -An Invocation Pointer references a specific [Invocation], either directly by CID (absolute), or from inside the Invocation itself (relative). - -```ipldsch -type InvocationPointer union { - | "/" -- Relative to the current invocation - | &Invocation -} -``` - -An Invoked Task Pointer references a specific Task inside a Batch, by the name of the label. If the Batch is unlabelled (a `List`), then the index represented as a string MUST be used. - -```ipldsch -type InvokedTaskPointer struct { - envl InvocationPointer - label String -} representation tuple -``` - -## 7.2 DAG-JSON Examples - -### 7.2.1 Relative - -#### 7.2.1.1 Named - -This relative pointer: - -```json -["/", "some-label"] -``` - -Will select the marked fields in these Named invocations: - -```json -{ - "uiv": "0.1.0", - "nnc": "6c*97-3=", - "run": { - "some-label": { - // <- Selects this - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - "some-other-label": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high", - "dev/notes": { - "select-task": ["/", "some-label"] // <- Pointer here - } - } - } - }, - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "sig": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - -```json -{ - "uiv": "0.1.0", - "nnc": "myNonce529", - "run": { - "some-label": { // <- Selects this - "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", - "do": "wasm/run", - "inputs": { - "func": "add_one", - "args": [42] - } - } - }, - "meta": { - "dev/notes": { - "select-task": ["/", "some-label"] // <- Pointer here - } - } - "prf": [{"/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4"}], - "sig": {"/": {"bytes:": "LcZglimIwQ58T0rnkErYshq2S8MMF9G/zRqYXv/PmXs="}} -} -``` - -### 7.2.1.1 List - -This local pointer: - -```json -["/", "1"] -``` - -Will select the marked fields in this List invocation: - -```json -{ - "uiv": "0.1.0", - "nnc": "6c*97-3=", - "run": [ - { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high", - "dev/notes": { - "select-task": ["/", "0"] // <- Pointer here - } - } - }, - // Selects this - // vvvvvvvvvvvv - { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - } - ], - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "sig": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - -### 7.2.2 Absolute - -#### 7.2.2.1 Named - -This absolute pointer: - -```json -[ - { "/": "bafkreiff4alf4rdi5mqg4fpxiejgotcnf2zksqanp5ctwzinmqyf7o3i2e" }, - "some-label" -] -``` - -Will select the marked field in this Named invocation: - -```json -// CID = bafkreiff4alf4rdi5mqg4fpxiejgotcnf2zksqanp5ctwzinmqyf7o3i2e -{ - "uiv": "0.1.0", - "nnc": "6c*97-3=", - "run": { - "some-label": { - // <- Selects this - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - }, - "some-other-label": { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high", - "dev/notes": { - "select-task": ["/", "some-label"] // <- Pointer here - } - } - } - }, - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "sig": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - -#### 7.2.2.1 List - -This absolute pointer: - -```json -[{ "/": "bafkreiew2p74l7bq3hnllbduzagdcezlab54ko4lpw72mfcvilh4ov2hkq" }, "1"] -``` - -Will select the marked field in this List invocation: - -```json -// CID = bafkreiew2p74l7bq3hnllbduzagdcezlab54ko4lpw72mfcvilh4ov2hkq -{ - "uiv": "0.1.0", - "nnc": "6c*97-3=", - "run": [ - { - "with": "mailto:akiko@example.com", - "do": "msg/send", - "inputs": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high", - "dev/notes": { - "select-task": ["/", "0"] // <- Pointer here - } - } - }, - // Selects this - // vvvvvvvvvvvv - { - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - } - } - ], - "prf": [ - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "sig": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } -} -``` - # 5 Promise > Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips @@ -1314,7 +964,9 @@ flowchart BR "with": "dns:example.com?TYPE=TXT", "do": "crud/update", "input": { "value": "hello world" }, - "prf": [{"/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u"}], + "prf": [ + { "/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u" } + ], "sig": { "/": { "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" @@ -1339,7 +991,9 @@ flowchart BR ] } }, - "prf": [{ "/": "bafyreialservj7dxazg4cskm5fuqwh5atgs54rgkvpmtkylbddygs37tce" }], + "prf": [ + { "/": "bafyreialservj7dxazg4cskm5fuqwh5atgs54rgkvpmtkylbddygs37tce" } + ], "sig": { "/": { "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" @@ -1364,7 +1018,9 @@ flowchart BR ] } }, - "prf": [{ "/": "bafyreifusp3qabhrzexltt6ausy4cz7t3cjxcnmwyiqkon5iuthx4h5uo4" }], + "prf": [ + { "/": "bafyreifusp3qabhrzexltt6ausy4cz7t3cjxcnmwyiqkon5iuthx4h5uo4" } + ], "sig": { "/": { "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" @@ -1386,19 +1042,25 @@ flowchart BR "_": [ { "<-": [ - { "/": "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4" }, + { + "/": "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4" + }, "ok" ] }, { "<-": [ - { "/": "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u" }, + { + "/": "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u" + }, "ok" ] } ] }, - "prf": [{ "/": "bafyreihwfiwuv4f2sajj7r247rezqaarhydd7ffod4tcesdv2so5nkmq7y" }], + "prf": [ + { "/": "bafyreihwfiwuv4f2sajj7r247rezqaarhydd7ffod4tcesdv2so5nkmq7y" } + ], "sig": { "/": { "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" @@ -1406,59 +1068,6 @@ flowchart BR } } } -{ - "v": "0.1.0", - "nnc": "abcdef", - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, - { "/": "bafkreibbz5pksvfjyima4x4mduqpmvql2l4gh5afaj4ktmw6rwompxynx4" } - ], - "run": { - "update-dns": { - "with": "dns:example.com?TYPE=TXT", - "do": "crud/update", - "inputs": { "value": "hello world" } - }, - "notify-bob": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": { - "to": "bob@example.com", - "subject": "DNSLink for example.com", - "body": { "promise/ok": ["/", "update-dns"] } - } - }, - "notify-carol": { - "with": "mailto://alice@example.com", - "do": "msg/send", - "inputs": { - "to": "carol@example.com", - "subject": "Hey Carol, DNSLink was updated!", - "body": { "promise/ok": ["/", "update-dns"] } - } - }, - "log-as-done": { - "with": "https://example.com/report", - "do": "crud/update", - "inputs": { - "payload": { - "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com", "carol@example.com"], - "event": "email-notification" - }, - "_": [ - { "promise/ok": ["/", "notify-bob"] }, - { "promise/ok": ["/", "notify-carol"] } - ] - } - } - }, - "sig": { - "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" - } - } -} ``` # 6 Prior Art From e71dcbae327aa165bc5a6b7785de6d2ed1f55b03 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 02:31:52 -0800 Subject: [PATCH 05/16] chore: move schema syntax note --- invocation.md | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/invocation.md b/invocation.md index 4581894..c3fd32f 100644 --- a/invocation.md +++ b/invocation.md @@ -132,7 +132,28 @@ The JSON examples below are given in [DAG-JSON], but UCAN Task is actually defin This format help disambiguate type information in generic [DAG-JSON] tooling. However, your presentation need not be in this specific format, as long as it can be converted to and from this cleanly. As it is used for the signature format, [DAG-CBOR] is RECOMMENDED. -## 1.4 Signatures +## 1.4 Note on Schema Syntax + +We use [IPLD Schema] syntax extended with generics. Standard IPLD Schema can be derived by ignoring parameters enclosed in angle brackets and interpreting parameters as `Any`. + +Below schema is in our extended syntax + +```ipldsch +type Box struct { + value T +} +``` + +Above schema compiled to standard syntax + +```ipldsch +type Box struct { + value Box_T +} +type Box_T any +``` + +## 1.5 Signatures All payloads described in this spec MUST be signed with a [Varsig]. @@ -184,22 +205,6 @@ A [promise] is a reference to expected result of the invocation receipt. ## 2.3 IPLD Schema -> ℹ️ We use extended [IPLD Schema] notation with generics. Standard IPLD Schema can be derived by ignoring parameters enclosed in angle brackets and interpreting them `Any`. Example below illustrates same type first in extended syntax and then same type in standard syntax -> -> ```ipldsch -> type Box struct { -> value T -> } -> ``` -> -> Is equivalent of -> -> ```ipldsch -> type Box struct { -> value Any -> } -> ``` - ```ipldsch type Invocation struct { v SemVer From 8e70902d1006862e3e4f80d97073dab9cb47c879 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 09:29:32 -0800 Subject: [PATCH 06/16] remove delimiter from pipelining operator --- invocation.md | 81 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/invocation.md b/invocation.md index c3fd32f..c2cd47f 100644 --- a/invocation.md +++ b/invocation.md @@ -226,7 +226,7 @@ type Invocation struct { } type Receipt struct { - src &Invocation + job &Invocation # output of the invocation out Out @@ -244,26 +244,38 @@ type Receipt struct { s Varsig # Proof that `iss` was authorized to by invocation `aud` to issue - # receipts. Can be omitted if `src.aud === this.iss` + # receipts. Can be omitted if `job.aud === this.iss` prf [&UCAN] implicit ([]) } # Promise is a way to reference data from the output of the invocation -type Promise union { - Await "<-" -} +type Promise struct { + Await "<-" +} representation keyed -type Await struct { - # promise to select from - invocation InvocationReference - # `/` delimited path from the `out` of the `Receipt`. - # If omitted implies whole `out` field. - selector optional String + +type Await union { + # Invocation reference + | &Invocation + # Inline invocation + | Invocation + # Specific invocation output + | ResultSelector +} representation kinded + +type ResultSelector struct { + job InvocationReference + at Selector } representation tuple +type Selector union { + | Key String + | Path [String] +} kinded + type InvocationReference union { - | Invocation - | &Invocation + | Invocation + | &Invocation } representation kinded ``` @@ -610,7 +622,7 @@ Receipts MUST use the same version as the invocation that they contain. ```ipldsch type Receipt struct { - src &Invocation + job &Invocation # output of the invocation out Out @@ -625,7 +637,7 @@ type Receipt struct { iss Principal # Proof that `iss` was authorized to by invocation `aud` to issue - # receipts. Can be omitted if `src.aud === this.iss` + # receipts. Can be omitted if `job.aud === this.iss` prf [&UCAN] implicit ([]) # Signature from the `iss`. @@ -637,7 +649,7 @@ type Receipt struct { ### 4.2.1 Invocation -The `src` field MUST include a link to the Invocation that the Receipt is for. +The `job` field MUST include a link to the Invocation that the Receipt is for. ### 4.2.2 Output @@ -719,7 +731,7 @@ The `s` field MUST contain a [Varsig] of the receipt payload, a receipt without ```json { - "src": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, + "job": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, "out": { "ok": [ { @@ -916,21 +928,34 @@ An invocation MUST fail if promise is resolved does not match the supplied branc The `Promise` describes a pointer to the eventual value in a Promise. When the `selector` of the `Await` omitted pointer resolves to the invocation result (`out` of the Receipt), otherwise it resolves to the specified branch `ok` on success and `error` on failure. Selector could also be used to resolve value nested deeper in any branch. ```ipldsch -type Promise union { - Await "<-" -} +# Promise is a way to reference data from the output of the invocation +type Promise struct { + Await "<-" +} representation keyed + -type Await struct { - # promise to select from - invocation InvocationReference - # `/` delimited path from the `out` of the `Receipt`. - # If omitted implies whole `out` field. - selector optional String +type Await union { + # Invocation reference + | &Invocation + # Inline invocation + | Invocation + # Specific invocation output + | ResultSelector +} representation kinded + +type ResultSelector struct { + job InvocationReference + at Selector } representation tuple +type Selector union { + | Key String + | Path [String] +} kinded + type InvocationReference union { - | Invocation - | &Invocation + | Invocation + | &Invocation } representation kinded ``` From 90cdb895b0f3e0fec09163a5c8e7acb103a17cac Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 09:30:30 -0800 Subject: [PATCH 07/16] allow invocation without redelegation --- invocation.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/invocation.md b/invocation.md index c2cd47f..1f4add0 100644 --- a/invocation.md +++ b/invocation.md @@ -432,7 +432,10 @@ UCAN capability provided in proofs MAY impose certain constraint on the type of ### 3.2.7 Proofs -The `prf` field MUST contain links to any UCANs that provide the authority to perform the invocation. All of the outermost `aud` fields MUST be set to the [Executor]'s DID. All of the outermost `iss` field MUST be set to the [Invoker]'s DID. +The `prf` field MUST contain links to any UCANs that provide the authority to perform the invocation. All of the outermost proofs MUST either + +1. Set `aud` fields to the [Executor]'s DID and `iss` field set to the [Invoker]'s DID, allowing Executor to (re)delegate enclosed capabilities. +2. Set `aud` field to the [Invoker]'s DID, preventing Executor from (re)delegating enclosed capabilities. ### 3.2.8 Nonce From 23e4465d6bb609f70e562b1b4688fe88bc526a99 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 12:28:07 -0800 Subject: [PATCH 08/16] fix: remove trailing , --- invocation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invocation.md b/invocation.md index 1f4add0..fa5ba6f 100644 --- a/invocation.md +++ b/invocation.md @@ -609,7 +609,7 @@ Batch invocation is simply passing promises as inputs to an invocation "/": { "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" } - }, + } } ``` From 3d5e63372a69084de7b60f78139acc2882ac1a8b Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 12:32:51 -0800 Subject: [PATCH 09/16] fix: JSON syntax errors --- invocation.md | 100 +++++++++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/invocation.md b/invocation.md index fa5ba6f..ed5a0e5 100644 --- a/invocation.md +++ b/invocation.md @@ -555,54 +555,70 @@ Batch invocation is simply passing promises as inputs to an invocation "do": "control/batch", "input": { "publishPost": { - "<-": [{ - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "https://example.com/blog/posts", - "do": "crud/create", - "input": { - "headers": { - "content-type": "application/json" + "<-": [ + { + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + { + "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" + } + ] }, - "prf": [ - {"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"} - ] - }, - "ok" - ], + "ok" + ] + }, "sendEmail": { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "mailto:akiko@example.com", - "do": "msg/send", - "input": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, - "prf": [ - {"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"} + "<-": [ + { + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "with": "mailto:akiko@example.com", + "do": "msg/send", + "input": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" + }, + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + }, + "prf": [ + { + "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" + } + ] + }, + "ok" ] }, "sendTextMessage": { - "/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle" + "<-": [ + { + "/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle" + }, + "ok" + ] } }, "s": { From 4fcd0886854ad72b4e7329e5cec4508540f75625 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 12:35:19 -0800 Subject: [PATCH 10/16] chore: rename action to be more specific --- invocation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invocation.md b/invocation.md index ed5a0e5..03372e0 100644 --- a/invocation.md +++ b/invocation.md @@ -552,7 +552,7 @@ Batch invocation is simply passing promises as inputs to an invocation "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", "aud": "did:web:ucan.run", "with": "did:web:ucan.run", - "do": "control/batch", + "do": "data/identity", "input": { "publishPost": { "<-": [ From 90e9b9a99d421f6a5fd6b192e164a3c9b164d09e Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Thu, 12 Jan 2023 12:40:40 -0800 Subject: [PATCH 11/16] chore: make it even more clear --- invocation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invocation.md b/invocation.md index 03372e0..c9b6599 100644 --- a/invocation.md +++ b/invocation.md @@ -551,8 +551,8 @@ Batch invocation is simply passing promises as inputs to an invocation "v": "0.1.0", "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", "aud": "did:web:ucan.run", - "with": "did:web:ucan.run", - "do": "data/identity", + "with": "javascript:(data) => data", + "do": "js/call", "input": { "publishPost": { "<-": [ From a8d507e9ee319d617384274808d56b77b5a430e6 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 25 Jan 2023 16:34:00 -0800 Subject: [PATCH 12/16] update spec to use group auth (#35) Updates schema and example as per sketch https://www.tldraw.com/r/1673637514425 with @expede --- invocation.md | 533 ++++++++++++++++++++++---------------------------- 1 file changed, 229 insertions(+), 304 deletions(-) diff --git a/invocation.md b/invocation.md index c9b6599..a8a064d 100644 --- a/invocation.md +++ b/invocation.md @@ -207,24 +207,49 @@ A [promise] is a reference to expected result of the invocation receipt. ```ipldsch type Invocation struct { - v SemVer + v SemVer - iss Principal - aud Principal + with URI + do Ability + input In (implicit {}) - with URI - do Ability - input In (implicit {}) + meta {String : Any} (implicit {}) - meta {String : Any} (implicit {}) + prf [&UCAN] + nnc optional String + nbf optional Int - prf [&UCAN] - nnc optional String - nbf optional Int + auth &Auth +} - s Varsig +# Invocation ID represents fields of the Invocation by which +# it can be uniquely identified. +type InvocationID struct { + v Semver + with URI + do Ability + input In (implicit {}) + + meta {String : Any} (implicit {}) + + prf [&UCAN] + nnc optional String + nbf optional Int } +# Authorization signed by invoker (iss) for the executor (aud) +# for running any of the invocations inside the `run` field. +type Auth { + iss Principal + aud Principal + # CIDs MUST be sorted alphabetically + run [&InvocationID] + + # Signature from the `iss` + s VarSig +} + + type Receipt struct { job &Invocation @@ -257,26 +282,19 @@ type Promise struct { type Await union { # Invocation reference | &Invocation - # Inline invocation - | Invocation # Specific invocation output | ResultSelector } representation kinded type ResultSelector struct { - job InvocationReference + job &Invocation at Selector } representation tuple type Selector union { - | Key String + | Branch String | Path [String] } kinded - -type InvocationReference union { - | Invocation - | &Invocation -} representation kinded ``` # 3 Invocation @@ -287,25 +305,29 @@ Using the JavaScript analogy from the introduction, an invocation is similar to ```json { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - - "with": "mailto:alice@example.com", - "do": "msg/send", - "inputs": { - "to": ["bob@example.com", "carol@example.com"], - "subject": "hello", - "body": "world" - }, - - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + "bafy...auth": { + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "run": [{ "/": "bafy...msgSendID" }], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } } + }, + "bafy...msgSend": { + "v": "0.1.0", + "with": "mailto:alice@example.com", + "do": "msg/send", + "inputs": { + "to": ["bob@example.com", "carol@example.com"], + "subject": "hello", + "body": "world" + }, + "auth": { "/": "bafy...auth" }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ] } } ``` @@ -323,40 +345,38 @@ Later, when we explore [Promise]s, this also includes capturing the promise: ```json { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "mailto://alice@example.com", - "do": "msg/send", - "input": { - "to": { - "<-": [ - { - "v": "0.1.0", - "iss": "did:key:zAlice", - "aud": "did:web:ucan.run", - "with": "https://example.com/mailinglist", - "do": "crud/read", - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, - "prf": [{"/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a"}] - }, - "ok" - ], + "bafy...auth": { + "iss": "did:key:zAlice", + "aud": "did:web:ucan.run", + "run": [{ "/": "bafy...crudReadID" }, { "/": "bafy...msgSend" }], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } } - "subject": "hello", - "body": "world" }, - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "bafy...crudRead": { + "v": "0.1.0", + "with": "https://example.com/mailinglist", + "do": "crud/read", + "auth": { "/": "bay...auth" }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ] + }, + "bafy...msgSend": { + "v": "0.1.0", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": { "<-": [{ "/": "bafy...crud" }, "ok"] }, + "subject": "hello", + "body": "world" + }, + "prf": [ + { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + ], + "auth": { "/": "bay...auth" } } } ``` @@ -489,8 +509,6 @@ Sending Email: ```json { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "mailto:akiko@example.com", "do": "msg/send", "input": { @@ -502,10 +520,8 @@ Sending Email: "dev/tags": ["friends", "coffee"], "dev/priority": "high" }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "auth": { + "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }, "prf": [ { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } @@ -518,8 +534,6 @@ Running WebAssembly from binary: ```json { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", "do": "wasm/run", "input": { @@ -533,14 +547,12 @@ Running WebAssembly from binary: "gas": 5000 } }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, "prf": [ { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } - ] + ], + "auth": { + "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" + } } ``` @@ -548,83 +560,63 @@ Batch invocation is simply passing promises as inputs to an invocation ```json { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "javascript:(data) => data", - "do": "js/call", - "input": { - "publishPost": { - "<-": [ - { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "https://example.com/blog/posts", - "do": "crud/create", - "input": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, - "prf": [ - { - "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" - } - ] - }, - "ok" - ] + "bafy...auth": { + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "run": [{ "/": "bafy...post" }, { "/": "bafy...msg" }], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } + } + }, + "bafy...post": { + "v": "0.1.0", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } }, - "sendEmail": { - "<-": [ - { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "mailto:akiko@example.com", - "do": "msg/send", - "input": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, - "prf": [ - { - "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" - } - ] - }, - "ok" - ] + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ], + "auth": { "/": "bafy...auth" } + }, + "bafy...msg": { + "v": "0.1.0", + "with": "mailto:akiko@example.com", + "do": "msg/send", + "input": { + "to": ["boris@example.com", "carol@example.com"], + "subject": "Coffee", + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" }, - "sendTextMessage": { - "<-": [ - { - "/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle" - }, - "ok" - ] - } + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ], + "auth": { "/": "bafy...auth" } }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "bafy...batch": { + "v": "0.1.0", + "with": "javascript:(data) => data", + "do": "js/call", + "input": { + "publishPost": { "<-": [{ "/": "bafy...post" }, "ok"] }, + "sendEmail": { "<-": [{ "/": "bafy...msg" }, "ok"] }, + "sendTextMessage": { "<-": [{ "/": "bafy...text" }, "ok"] } + }, + "prf": [ + { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + ], + "auth": { "/": "bafy...auth" } } } ``` @@ -796,8 +788,6 @@ For example, consider the following invocation batch: ```json { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "mailto:akiko@example.com", "do": "msg/send", @@ -805,31 +795,23 @@ For example, consider the following invocation batch: "inputs": { "to": { "<-": [ - { - "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" - }, + { "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" }, "ok" ] }, "subject": "Coffee", "body": { - "promise/ok": [ - { - "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" - }, + "<-": [ + { "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" }, "ok" ] } - } + }, "prf": [ { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } + "auth": { "/": "bafy...auth" } } ``` @@ -839,76 +821,55 @@ Which is roughly equivalent of the of the following invocation, which inlines ab ```json { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - - "with": "mailto:akiko@example.com", - "do": "msg/send", - - "inputs": { - "to": { - "<-": [ - { - "meta": { "name": "create-draf" }, - - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "with": "https://example.com/blog/posts", - "do": "crud/create", - "inputs": { - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything..." - } - }, - "prf": [ - "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } - }, - "ok" - ] - }, - "subject": "Coffee", - "body": { - "promise/ok": [ - { - "meta": { "name": "get-editors" }, - - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - - "with": "https://example.com/users/editors", - "do": "crud/read" - - "prf": [ - "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } - }, - "ok" - ] + "bafy...atuh": { + "v": "0.1.0", + "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", + "aud": "did:web:ucan.run", + "run": [ + { "/": "bafy...mkDraft" }, + { "/": "bafy...edtrs" }, + { "/": "bafy...msgSend" } + ], + "s": { + "/": { + "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" + } } }, - - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "bafy...mkDraft": { + "meta": { "name": "create-draf" }, + "v": "0.1.0", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything..." + } + }, + "prf": [{"/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu"}], + "auth": {"/": "bafy...auth"} + }, + "bafy...edtrs": { + "meta": { "name": "get-editors" }, + "v": "0.1.0", + "with": "https://example.com/users/editors", + "do": "crud/read", + "prf": [{"/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle"}], + "auth": {"/": "bafy...auth"} + }, + "bafy...msgSend": { + "v": "0.1.0", + "with": "mailto:akiko@example.com", + "do": "msg/send", + "input": { + "to": { "<-": [{ "/": "bafy...mkDraft" }, "ok"] }, + "subject": "Coffee", + "body": { "<-": [{ "/": "bafy...edtrs" }, "ok"] } + }, + "prf": [{ "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }] + ], + "auth": {"/": "bafy...auth"} } } ``` @@ -1006,15 +967,15 @@ flowchart BR ```json { - "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce": { - "v": "0.1.0", + "bafy...auth": { "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", "aud": "did:web:ucan.run", - "with": "dns:example.com?TYPE=TXT", - "do": "crud/update", - "input": { "value": "hello world" }, - "prf": [ - { "/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u" } + "run": [ + { "/": "bady...crudUp" }, + { "/": "bady...dns" }, + { "/": "bafy...bob" }, + { "/": "bafy...carol" }, + { "/": "bafy...log" } ], "sig": { "/": { @@ -1022,64 +983,46 @@ flowchart BR } } }, - "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4": { + "bafy...dns": { + "v": "0.1.0", + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "input": { "value": "hello world" }, + "prf": [ + { "/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u" } + ], + "auth": { "/": "bafy...auth" } + }, + "bafy...bob": { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "mailto://alice@example.com", "do": "msg/send", "input": { "to": "bob@example.com", "subject": "DNSLink for example.com", - "body": { - "<-": [ - { - "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" - }, - "ok" - ] - } + "body": { "<-": [{ "/": "bafy...dns" }, "ok"] } }, "prf": [ { "/": "bafyreialservj7dxazg4cskm5fuqwh5atgs54rgkvpmtkylbddygs37tce" } ], - "sig": { - "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" - } - } + "auth": { "/": "bafy...auth" } }, - "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u": { + "bafy...carol": { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "mailto://alice@example.com", "do": "msg/send", "input": { "to": "carol@example.com", "subject": "Hey Carol, DNSLink was updated!", - "body": { - "<-": [ - { - "/": "bafyreifngx2r7ifssddx5ohdxsn2x5ukfhh6rxy6hswulkxbn6yw6f7jce" - }, - "ok" - ] - } + "body": { "<-": [{ "/": "bafy...dns" }, "ok"] } }, "prf": [ { "/": "bafyreifusp3qabhrzexltt6ausy4cz7t3cjxcnmwyiqkon5iuthx4h5uo4" } ], - "sig": { - "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" - } - } + "auth": { "/": "bafy...auth" } }, - "bafyreigr4evtgj6zojwhvceurdbpclm2wrftka5snymvze53fcpbtmiaeq": { + "bafy...log": { "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "https://example.com/report", "do": "crud/update", "input": { @@ -1089,32 +1032,14 @@ flowchart BR "event": "email-notification" }, "_": [ - { - "<-": [ - { - "/": "bafyreigb4gzn3ghfownvf5u6tqv4gjb247ai4fbv56nadtcpfznh37y5p4" - }, - "ok" - ] - }, - { - "<-": [ - { - "/": "bafyreidqviro3x5pxy6kneolt6qjdorva46ayrtifeouhyudnxhucqqe7u" - }, - "ok" - ] - } + { "<-": [{ "/": "bafy...bob" }, "ok"] }, + { "<-": [{ "/": "bafy...carol" }, "ok"] } ] }, "prf": [ { "/": "bafyreihwfiwuv4f2sajj7r247rezqaarhydd7ffod4tcesdv2so5nkmq7y" } ], - "sig": { - "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" - } - } + "auth": { "/": "bafy...auth" } } } ``` From 443fa8152e39fd2ceb23394d00a60d322f1b1f62 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Sun, 29 Jan 2023 23:06:24 -0800 Subject: [PATCH 13/16] update spec with group auth --- invocation.md | 1289 +++++++++++++++++++++++++++++-------------------- 1 file changed, 775 insertions(+), 514 deletions(-) diff --git a/invocation.md b/invocation.md index a8a064d..ebc0cdb 100644 --- a/invocation.md +++ b/invocation.md @@ -2,8 +2,8 @@ ## Editors -- [Irakli Gozalishvili](https://github.com/Gozala), [DAG House](https://dag.house/) - [Brooklyn Zelenka](https://github.com/expede/), [Fission](https://fission.codes/) +- [Irakli Gozalishvili](https://github.com/Gozala), [DAG House](https://dag.house/) ## Authors @@ -19,7 +19,7 @@ # 0 Abstract -UCAN Execution defines a format for expressing the intention to execute delegated UCAN capabilities, the attested receipts from an execution, and how to extend computation via promise pipelining. +UCAN Invocation defines a format for expressing the intention to execute delegated UCAN capabilities, the attested receipts from an execution, and how to extend computation via promise pipelining. > This is based on [UCAN invocation] specification, altering it slightly in order to make tasks self-contained as discussed in [#6](https://github.com/ucan-wg/invocation/issues/6) @@ -134,9 +134,9 @@ This format help disambiguate type information in generic [DAG-JSON] tooling. Ho ## 1.4 Note on Schema Syntax -We use [IPLD Schema] syntax extended with generics. Standard IPLD Schema can be derived by ignoring parameters enclosed in angle brackets and interpreting parameters as `Any`. +We use [IPLD Schema] syntax extended with generics. Standard IPLD Schema can be derived by ignoring parameters enclosed in angle brackets and interpreting parameters as `any`. -Below schema is in our extended syntax +Below schema is in the extended syntax ```ipldsch type Box struct { @@ -144,7 +144,7 @@ type Box struct { } ``` -Above schema compiled to standard syntax +It compiles down to a following standard syntax ```ipldsch type Box struct { @@ -172,172 +172,320 @@ Task adds two new roles to UCAN: invoker and executor. The existing UCAN delegat The invoker signals to the executor that a task associated with a UCAN SHOULD be performed. -The invoker MUST be the UCAN delegator. Their DID MUST be authenticated in the `iss` field of the contained UCAN. +The invoker MAY be specified explicitly using `iss` field. If `iss` field is omitted invoker is specified implicitly using `iss` field of the contained UCAN authorizing the invocation. ### 2.1.2 Executor The executor is directed to perform some task described in the UCAN by the invoker. -The executor MUST be the UCAN delegate. Their DID MUST be set the in `aud` field of the contained UCAN. +The executor MAY be specified explicitly using `aud` field. If `aud` field is omitted executor is specified implicitly using `aud` field of the contained UCAN authorizing the invocation. ## 2.2 Components ```mermaid -flowchart LR - U(UCAN) -- Auth --> I - I(Invocation) -- Output --> O(Result) - O -- Consume --> P(Promise) - O -- Attest --> R(Receipt) - R -- Short-circuit --> M(Memoization) -``` +stateDiagram-v2 +direction TB -### 2.2.1 Invocation +auth: Authorization -An [Invocation] is like a cryptographically signed function application, a request to perform some action on a resource with specific input. Invocation MAY have optional metadata that is not used to describe the meaning of the computation or effect to be run. Executor MUST produce same result regardless of the metadata. +send: Invocation +send.task: Task +send.promise: Promise +send.receipt: Receipt -### 2.2.2 Receipt +read: Invocation +read.task: Task -A [Receipt] describes the output of an invocation. It is referenced either by the invocation itself or an IPLD Link of the invocation. +update: Invocation +update.task: Task -### 2.2.3 Promise +send.proof: UCAN -A [promise] is a reference to expected result of the invocation receipt. +state auth { + direction TB -## 2.3 IPLD Schema + state run <> -```ipldsch -type Invocation struct { - v SemVer + sig-->iss: iss + sig-->aud: aud + sig-->meta: meta + sig-->nnc: nnc + sig-->run: run - with URI - do Ability - input In (implicit {}) - meta {String : Any} (implicit {}) - prf [&UCAN] - nnc optional String - nbf optional Int + iss: did꞉key꞉z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi + aud: did꞉web꞉ucan.run + meta: {} + nnc: "some nonce" +} + +state send.task { + direction LR + + send.task.prf: prf + send.task.do: do + send.task.with: with + send.task.input: input + + send.do: msg/send + send.with: mailto꞉//alice@example.com + send.input: {"to"꞉ "bob@example.com", "subject"꞉ "DNSLink for example.com"} + send.prf: UCAN + + send.task.do --> send.do + send.task.with --> send.with + send.task.input --> send.input + send.task.prf --> send.prf +} + +state read.task { + direction LR + + read.task.do: do + read.task.with: with + read.task.prf: prf + + + read.do: crud/read + read.with: https꞉//example.com/mailinglist + read.prf1: UCAN + read.prf2: UCAN + + read.task.do --> read.do + read.task.with --> read.with + read.task.prf --> read.prf1 + read.task.prf --> read.prf2 +} + +state update.task { + direction LR + + update.task.do: do + update.task.with: with + update.task.input: input + update.task.prf: prf + update.task.meta: meta - auth &Auth + update.can: crud/update + update.with: dns꞉example.com?TYPE=TXT + update.input: {value꞉"hello world"} + update.prf: UCAN + update.meta: {dev/tags"꞉ ["friends", "coffee"]} + + update.task.do --> update.can + update.task.with --> update.with + update.task.input --> update.input + update.task.prf --> update.prf + update.task.meta --> update.meta + +} + +state send { + send.run: task + send.auth: authorization +} + +state read { + read.run: task + read.auth: authorization +} + +state update { + update.run: task + update.auth: authorization +} + + +state send.promise { + state send.promise.await <> +} + +state send.receipt { + send.receipt.sig: sig + send.receipt.ran: ran + send.receipt.out: result + send.receipt.meta: meta + send.receipt.iss: iss + send.receipt.prf: prf + -- + + state send.result <> + send.result --> send.result.ok: ok + send.result --> send.result.error: error + + send.result.ok: success + send.result.error: { error } } -# Invocation ID represents fields of the Invocation by which -# it can be uniquely identified. -type InvocationID struct { - v Semver + +send.run --> send.task +send.auth --> auth + +read.run --> read.task +read.auth --> auth + +update.run --> update.task +update.auth --> auth + +run --> send.task +run --> read.task +run --> update.task + + +send.promise.await --> send: ok +send.promise.await --> send: error +send.promise.await --> send: * + + +send.receipt.out --> send.result +send.receipt.sig --> send.receipt.ran +send.receipt.sig --> send.receipt.out +send.receipt.sig --> send.receipt.meta +send.receipt.sig --> send.receipt.iss +send.receipt.sig --> send.receipt.prf + + +send.receipt.ran --> send +``` + +### 2.2.1 Task + +A [Task] is like a deferred function application: a request to perform some action on a resource with specific input. + +### 2.2.2 Authorization + +An [Authorization] is a cryptographically signed proof permitting execution of referenced tasks. It allows invoker to authorize a group of tasks using one cryptographic signature. + +### 2.2.3 Invocation + +An [Invocation] is an invoker authorized instruction to an executor to run the [Task]. + +### 2.2.4 Result + +A [Result] is the output of a [Task]. + +### 2.2.5 Receipt + +A [Receipt] is a cryptographically signed description of the [Invocation] output. + +### 2.2.6 Promise + +A [promise] is a reference to an eventual [Receipt] of an [Invocation]. + +## 2.3 IPLD Schema + +```ipldsch +type Task struct { with URI do Ability - input In (implicit {}) + input In (implicit {}) meta {String : Any} (implicit {}) - prf [&UCAN] - nnc optional String - nbf optional Int + prf [&UCAN] (implicit []) } -# Authorization signed by invoker (iss) for the executor (aud) -# for running any of the invocations inside the `run` field. -type Auth { - iss Principal - aud Principal +type Authorization struct { + v SemVer + # CIDs MUST be sorted alphabetically - run [&InvocationID] + run [&Task] (implicit []) + + # If omitted iss / aud MUST be derived from the + # delegated proof(s) + iss optional Principal + aud optional Principal + + meta {String : Any} (implicit {}) + nnc string (implicit "") - # Signature from the `iss` s VarSig } +type Invocation struct { + task &Task + authorization &Authorization +} representation tuple -type Receipt struct { - job &Invocation +type Receipt struct { + ran &Invocation # output of the invocation - out Out + out Result # Related receipts - origin optional &Receipt + origin optional &Receipt # All the other metadata - meta { String: Any } + meta { String: Any } (implicit {}) + + # Principal that issued this receipt. If omitted issuer is + # inferred from the invocation task audience. + iss optional Principal - # Principal that issued this receipt - iss Principal + # When issuer is different from executor this MUST hold a UCAN + # delegation chain from executor to the issuer. Should be omitted executor is an issuer. + prf [&UCAN] implicit ([]) # Signature from the `iss`. s Varsig +} - # Proof that `iss` was authorized to by invocation `aud` to issue - # receipts. Can be omitted if `job.aud === this.iss` - prf [&UCAN] implicit ([]) +type Result union { + | Ok ("ok") # Success + | Error ("error") # Error } -# Promise is a way to reference data from the output of the invocation -type Promise struct { - Await "<-" +# Promise is a way to reference invocation receipt +type Promise union { + &Invocation "await/invocation/*" + &Invocation "await/invocation/ok" + &Invocation "await/invocation/error" + # If it is a task you MUST derive invocation by using authorization of + # this invocation + &Task "await/task/*" + &Task "await/task/ok" + &Task "await/task/error" } representation keyed - -type Await union { - # Invocation reference - | &Invocation - # Specific invocation output - | ResultSelector -} representation kinded - -type ResultSelector struct { - job &Invocation - at Selector -} representation tuple - -type Selector union { - | Branch String - | Path [String] -} kinded +type URI string ``` -# 3 Invocation +# 3 Task -An invocation is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, input)` triple. The `input` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. +A Task is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, input)` triple. The `input` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. -Using the JavaScript analogy from the introduction, an invocation is similar to function application: +Using the JavaScript analogy from the introduction, a Task is similar to wrapping a call in an anonymous function: ```json { - "bafy...auth": { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "run": [{ "/": "bafy...msgSendID" }], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } - }, - "bafy...msgSend": { - "v": "0.1.0", - "with": "mailto:alice@example.com", - "do": "msg/send", - "inputs": { - "to": ["bob@example.com", "carol@example.com"], - "subject": "hello", - "body": "world" - }, - "auth": { "/": "bafy...auth" }, - "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "body": "world", + "subject": "hello", + "to": [ + "bob@example.com", + "carol@example.com" ] - } + }, + "prf": [ + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } + ] } ``` ```js // Pseudocode -msg.send("mailto:alice@example.com", { +() => msg.send("mailto:alice@example.com", { to: ["bob@example.com", "carol@example.com"], subject: "hello", - body: "world", + body: "world" +}, { + proofs: [ + CID.parse('bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha') + ] }) ``` @@ -345,141 +493,113 @@ Later, when we explore [Promise]s, this also includes capturing the promise: ```json { - "bafy...auth": { - "iss": "did:key:zAlice", - "aud": "did:web:ucan.run", - "run": [{ "/": "bafy...crudReadID" }, { "/": "bafy...msgSend" }], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } - }, - "bafy...crudRead": { - "v": "0.1.0", + "bafyreibfcowm533mjovwymxu5wqg7tiqddvez5atgocyyomll3ouykppfy": { "with": "https://example.com/mailinglist", "do": "crud/read", - "auth": { "/": "bay...auth" }, "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + { "/": "bafyreib2dldcev74g5i76bacd4w72gowuraouv2kvnwa2ympvrvtybcsfi" } ] }, - "bafy...msgSend": { - "v": "0.1.0", + "bafyreif3k6kz4pip7wu6j26kdrrju3tbttcqhcmibdy6rpolazv6vds2kq": { "with": "mailto://alice@example.com", "do": "msg/send", "input": { - "to": { "<-": [{ "/": "bafy...crud" }, "ok"] }, + "body": "world", "subject": "hello", - "body": "world" + "to": { + "await/task/*": { + "/": "bafyreibfcowm533mjovwymxu5wqg7tiqddvez5atgocyyomll3ouykppfy" + } + } }, "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } - ], - "auth": { "/": "bay...auth" } + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } + ] } } ``` ```js // Pseudocode -const mailingList = crud.read("https://exmaple.com/mailinglist", {}) +const mailingList = crud.read("https://exmaple.com/mailinglist", {}, { + proofs: [ + CID.parse('bafyreib2dldcev74g5i76bacd4w72gowuraouv2kvnwa2ympvrvtybcsfi') + ] +}) msg.send("mailto:alice@example.com", { - to: mailingList.catch(error => error).then(result => result.ok), + to: (await mailingList).ok, subject: "hello", body: "world", +}, { + proofs: [ + CID.parse('bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha') + ] }) ``` ## 3.1 Schema ```ipldsch -type Invocation struct { - v SemVer - - iss Principal - aud Principal - - with URI - do Ability - input In (implicit {}) - - meta {String : Any} (implicit {}) +type Task struct { + with URI + do Ability - prf [&UCAN] - nnc optional String - nbf optional Int + input {String: Any} (implicit {}) + meta {String : Any} (implicit {}) - s Varsig + prf [&UCAN] (implicit []) } + +type URI string ``` ## 3.2 Fields -An Invocation authorizes execution of the capability. There are a few invariants that MUST hold between the `iss`, `aud`, `prf` and `sig` fields: +### 3.2.1 Resource -- The `iss` MUST be the Invoker. -- All of the `prf` UCANs MUST list the Invoker in their `iss`. -- All of the `prf` UCANs MUST list the Executor in their `aud` field, grating it authority to perform some action on a resource, or be the root authority for it. -- The `sig` field MUST be produced by the Invoker -- Invocation MUST be provably authorized by the UCANs in the `prf` field. +The `with` field MUST contain the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the resource being accessed. If the resource being accessed is some static data, it is RECOMMENDED to reference it by the [`data`](https://en.wikipedia.org/wiki/Data_URI_scheme), [`ipfs`](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls), or [`magnet`](https://en.wikipedia.org/wiki/Magnet_URI_scheme) URI schemes. -### 3.2.1 UCAN Invocation Version +### 3.2.2 Ability -The `v` field MUST contain the SemVer-formatted version of the UCAN Invocation Specification that this struct conforms to. +The `do` field MUST contain a [UCAN Ability](https://github.com/ucan-wg/spec/#23-ability). This field can be thought of as the message or trait being sent to the resource. -### 3.2.2 Invoker +### 3.2.3 Input -The `iss` field MUST be a principal authorizing the invocation. It MUST be encoded in format describe in [UCAN-IPLD]. +The OPTIONAL `input` field, MAY contain any parameters expected by the URI/Ability pair, which MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. -### 3.2.3 Executor +If present, `input` field MUST have an IPLD [map representation][ipld representation], and thus MAY be a: -The `aud` field MUST be a principal authorized to perform the invocation. It MUST be encoded in format described in [UCAN-IPLD]. +1. [struct](https://ipld.io/docs/schemas/features/representation-strategies/#struct-map-representation) in map representation. +2. [keyed](https://ipld.io/docs/schemas/features/representation-strategies/#union-keyed-representation), [enveloped](https://ipld.io/docs/schemas/features/representation-strategies/#union-envelope-representation) or [inline](https://ipld.io/docs/schemas/features/representation-strategies/#union-inline-representation) union. +3. [unit](https://github.com/ipld/ipld/blob/353baf885adebb93191cbe1f7be34f0517e20bbd/specs/schemas/schema-schema.ipldsch#L753-L789) in empty map representation. +3. [map](https://ipld.io/docs/schemas/features/representation-strategies/#map-map-representation) in map representation. -### 3.2.4 Resource -The `with` field MUST contain the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the resource being accessed. If the resource being accessed is some static data, it is RECOMMENDED to reference it by the [`data`](https://en.wikipedia.org/wiki/Data_URI_scheme), [`ipfs`](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls), or [`magnet`](https://en.wikipedia.org/wiki/Magnet_URI_scheme) URI schemes. +UCAN capabilities provided in [Proofs] MAY impose certain constraint on the type of `input` allowed. -### 3.2.5 Ability +If `input` field is not present, it is implicitly a `unit` represented as empty map. -The `do` field MUST contain a [UCAN Ability](https://github.com/ucan-wg/spec/#23-ability). This field can be thought of as the message or trait being sent to the resource. +### 3.2.4 Metadata -### 3.2.6 Input +The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. -The `input` field MUST contain any arguments expected by the URI/Ability pair. This MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. +If `meta` field is not present, it is implicitly a `unit` represented as an empty map. -UCAN capability provided in proofs MAY impose certain constraint on the type of `Input` allowed. -### 3.2.7 Proofs +### 3.2.5 Proofs -The `prf` field MUST contain links to any UCANs that provide the authority to perform the invocation. All of the outermost proofs MUST either +The `prf` field MUST contain links to any UCANs that provide the authority to perform the task. All of the outermost proofs MUST either 1. Set `aud` fields to the [Executor]'s DID and `iss` field set to the [Invoker]'s DID, allowing Executor to (re)delegate enclosed capabilities. 2. Set `aud` field to the [Invoker]'s DID, preventing Executor from (re)delegating enclosed capabilities. -### 3.2.8 Nonce - -If present, the OPTIONAL `nnc` field MAY include a random nonce expressed in ASCII. This field can ensures that multiple invocations are unique. - -### 3.2.9 Metadata - -The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. - -Data inside the `meta` field SHOULD NOT be used for [Receipt]s. - -### 3.2.10 Signature - -The `s` field MUST contain a [Varsig] of the invocation payload, an invocation without `meta` and `s` fields encoded in [DAG-CBOR]. ## 3.3 DAG-JSON Examples -Interacting with an HTTP API: +### 3.3.1 Interacting with an HTTP API ```json { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", "with": "https://example.com/blog/posts", "do": "crud/create", "input": { @@ -493,22 +613,16 @@ Interacting with an HTTP API: "draft": true } }, - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - }, "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } ] } ``` -Sending Email: +### 3.3.2 Sending Email ```json { - "v": "0.1.0", "with": "mailto:akiko@example.com", "do": "msg/send", "input": { @@ -517,179 +631,283 @@ Sending Email: "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" }, "meta": { - "dev/tags": ["friends", "coffee"], - "dev/priority": "high" - }, - "auth": { - "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" + "dev/priority": "high", + "dev/tags": ["friends", "coffee"] }, "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + { "/": "bafyreihvee5irbkfxspsim5s2zk2onb7hictmpbf5lne2nvq6xanmbm6e4" } ] } ``` -Running WebAssembly from binary: +### 3.3.3 Running WebAssembly ```json { - "v": "0.1.0", "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", "do": "wasm/run", "input": { "func": "add_one", "args": [42] }, - "meta": { - "dev/notes": "The standard Wasm demo", - "ipvm/verification": "attestation", - "ipvm/resources": { - "gas": 5000 - } - }, "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } + { "/": "bafyreibrqkoin6jzc35hnbrfbsenmcyvd26bn3xyq4of6iwk5z4h63qr34" } + ] +} +``` + +# 4 Authorization + + +An [Authorization] authorizes one or more Tasks to be run. There are a few invariants that MUST hold between the `run`, `iss`, `aud` and `sig` fields: + +- All of the `run` Tasks MUST be provably authorized by the UCANs in their `prf` field. +- All of the `run` Tasks MUST be authorized by the same [Invoker]. +- The [Executor] MUST be either explicitly specified using `aud` field or be inferred per task basis from the audience (`aud`) of the UCANs in their `prf` field. +- The [Invoker] MUST be either explicitly specified using `iss` field, or be inferred from the `run` Tasks by the issuer of the UCANs in their `prf` filed. +- The `sig` field MUST be produced by the [Invoker]. + + +## 4.1 Schema + +```ipldsch +type Authorization struct { + v SemVer + + # CIDs MUST be sorted alphabetically + run [&Task] (implicit []) + + # If omitted iss / aud MUST be derived from the + # delegated proof(s) + iss optional Principal + aud optional Principal + + meta {String : Any} (implicit {}) + nnc string (implicit "") + + s VarSig +} + +type SemVer string +``` +### 4.2 Fields + +### 4.2.1 UCAN Invocation Version + +The `v` field MUST contain the SemVer-formatted version of the UCAN Invocation Specification that this struct conforms to. + +#### 4.2.2 + +The `run` field MUST be a set of [Task] links been authorized. It SHOULD be encoded as an alphabetically ordered list without duplicates. + +If `run` field is omitted, it is implicitly a an empty list and has no practical use as it authorizes no tasks. + + +### 3.2.2 Invoker + +If present, the OPTIONAL `iss` field MUST be a principal authorizing task run and MUST be encoded in format describe in [UCAN-IPLD] specification. + +If `iss` field is omitted, Invoker MUST be inferred from the tasks by their [Proof]s `iss` field. All of the tasks MUST have a same invoker. + +### 3.2.3 Executor + +If present, the OPTIONAL `aud` filed MUST be a principal authorized to run the task. It MUST be encoded in format described in [UCAN-IPLD]. + +If `aud` field is omitted, Executor MUST be inferred per task from the [Proof]s `aud` field. + +### 3.3.3 Metadata + +The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. + +Data inside the `meta` field SHOULD NOT be used for [Receipt]s. + +If `meta` field is not present, it is implicitly a `unit` represented as an empty map. + +### 3.3.4 Nonce + +If present, the OPTIONAL `nnc` field MAY include a random nonce expressed in ASCII. This field can ensures that multiple invocations are unique. + + +### 3.3.5 Signature + +The `sig` field MUST contain a [Varsig] of the [CBOR] encoded `Authorization` block without `v` and `s` and `meta` fields. + +## 3.4 DAG-JSON Example + +### 3.4.1 Infer Issuer & Audience + +```json +{ + "v": "0.1.0", + "run": [ + { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, + { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } ], - "auth": { - "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" + "s": { + "/": { + "bytes": "7aEDQBIqKMpONi41J/8gYAaKM/yVef4jwQkL4czPlRVid7rLV+Ftk4EgInLHLpLF/xMrJOcnIqQMwSOW3wwJQEzS0wI" + } } } ``` -Batch invocation is simply passing promises as inputs to an invocation +### 3.4.2 With all the optionals ```json { - "bafy...auth": { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "run": [{ "/": "bafy...post" }, { "/": "bafy...msg" }], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } + "v": "0.1.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:web:ucan.run", + "run": [ + { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, + { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } + ], + "meta": { + "notes/personal": "I felt like making an invocation today!", + "ipvm/config": { + "time": [5, "minutes"], + "gas": 3000 } }, - "bafy...post": { - "v": "0.1.0", - "with": "https://example.com/blog/posts", - "do": "crud/create", - "input": { - "headers": { - "content-type": "application/json" - }, - "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything...", - "topics": ["authz", "journal"], - "draft": true - } - }, - "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } - ], - "auth": { "/": "bafy...auth" } - }, - "bafy...msg": { - "v": "0.1.0", - "with": "mailto:akiko@example.com", - "do": "msg/send", - "input": { - "to": ["boris@example.com", "carol@example.com"], - "subject": "Coffee", - "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!" - }, - "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } - ], - "auth": { "/": "bafy...auth" } - }, - "bafy...batch": { - "v": "0.1.0", - "with": "javascript:(data) => data", - "do": "js/call", - "input": { - "publishPost": { "<-": [{ "/": "bafy...post" }, "ok"] }, - "sendEmail": { "<-": [{ "/": "bafy...msg" }, "ok"] }, - "sendTextMessage": { "<-": [{ "/": "bafy...text" }, "ok"] } - }, - "prf": [ - { "/": "bafybeia3tspzaay4gcx3npcczgidbrsutq7yxnag3sfzrmvua6ogqjwy7a" } - ], - "auth": { "/": "bafy...auth" } + "nnc": "6c*97-3=", + "s": { + "/": { + "bytes": "7aEDQOQqEXrTp5HgGdYGQYhT+aLDB0IvBGDx161yAoN2EAsF/J0DwPY4j4bJstJX6x9sKy18JzVu1kGSdK10NSaWvAs" + } } } ``` -# 4 Receipt +# 4 Invocation -An Invocation Receipt is an attestation of the Result of an Invocation. A Receipt MUST be signed by the Executor (the `aud` of the associated UCANs) or it's delegate, in which case proof of delegation (of the invoked capability) from Executor to the Signer (the `iss` of the receipt) MUST be provided in `prf`. +As [noted in the introduction][lazy-vs-eager], there is a difference between a reference to a function and calling that function. [Tasks] are not executable until they have been authorized using signed [Authorization] from the [Invoker]. -**NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. +Invocation describes `(task, authorization)` tuple and which instructs [Executor] to run the [Task]. -Receipts MUST use the same version as the invocation that they contain. +The `authorization` MUST authorize the `task` - Linked [Authorization] MUST contain `task` in it's `run` field. ## 4.1 Schema ```ipldsch -type Receipt struct { - job &Invocation +type Invocation struct { + task &Task + authorization &Authorization +} representation tuple +``` - # output of the invocation - out Out +## 4.2 Fields - # Related receipts - origin optional &Receipt +### 4.2.1 - # All the other metadata - meta { String: Any } +The `task` field MUST contain a link to the [Task] to be run. - # Principal that issued this receipt - iss Principal +### 4.2.2 - # Proof that `iss` was authorized to by invocation `aud` to issue - # receipts. Can be omitted if `job.aud === this.iss` - prf [&UCAN] implicit ([]) +The `authorization` field MUST contain a link to the [Authorization] that authorizes invoked `task`. - # Signature from the `iss`. - s Varsig +## 4.2 DAG-JSON Example + +```json +{ + "blocks": { + "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu": { + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + }, + "prf": [ + { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } + ] + }, + "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi": { + "with": "mailto:akiko@example.com", + "do": "msg/send", + "input": { + "to": ["boris@example.com", "carol@example.com"], + "body": "Hey you two, I'd love to get coffee sometime and talk about UCAN Tasks!", + "subject": "Coffee" + }, + "meta": { + "dev/priority": "high", + "dev/tags": ["friends", "coffee"] + }, + "prf": [ + { "/": "bafyreihvee5irbkfxspsim5s2zk2onb7hictmpbf5lne2nvq6xanmbm6e4" } + ] + }, + "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by": { + "v": "0.1.0", + "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", + "aud": "did:web:ucan.run", + "run": [ + { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, + { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } + ], + "meta": { + "notes/personal": "I felt like making an invocation today!", + "ipvm/config": { + "gas": 3000, + "time": [5, "minutes"] + } + }, + "nnc": "6c*97-3=", + "s": { + "/": { + "bytes": "7aEDQOQqEXrTp5HgGdYGQYhT+aLDB0IvBGDx161yAoN2EAsF/J0DwPY4j4bJstJX6x9sKy18JzVu1kGSdK10NSaWvAs" + } + }, + }, + "bafyreiapsuwuvn5p3logmujov7fphukk7eih73nn5hqb6qlm6vvvf4mpmi": [ + { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, + { "/": "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by" } + ], + "bafyreigu7s6i6av4wztcnkjy5ptf6lxq7vvdujc25r7znpx7xb5wbpw4cy": [ + { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, + { "/": "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by" } + ] + }, + "roots": [ + { "/": "bafyreiapsuwuvn5p3logmujov7fphukk7eih73nn5hqb6qlm6vvvf4mpmi" }, + { "/": "bafyreigu7s6i6av4wztcnkjy5ptf6lxq7vvdujc25r7znpx7xb5wbpw4cy" } + ] } ``` -## 4.2 Fields +# 5 Result -### 4.2.1 Invocation +A `Result` records the output of a [Task], as well as its success or failure state. -The `job` field MUST include a link to the Invocation that the Receipt is for. - -### 4.2.2 Output - -The `out` field MUST contain the output of the invocation. It is RECOMMENDED that invocation produce output in [Result] format or it's extension with added type variants when needed, which allows selecting either success or failure branch during promise pipelining. - -### 4.2.2.1 Result - -A Result records success or a failure state of the [Invocation]. - -#### 4.2.2.1.1 Variants +## 5.1 Schema ```ipldsch -type Result union { - | T ("ok") # Success - | X ("error") # Failure -} representation keyed +type Result union { + | Ok ("ok") # Success + | Error ("error") # Error +} ``` -#### 4.2.2.1.2 Success +## 5.2 Variants + +## 5.2.1 Success -The success branch MUST contain the value returned from a successful invocation wrapped in the `"ok"` tag. The exact shape of the returned data is left undefined to allow for flexibility. +The success branch MUST contain the value returned from a successful [Task] wrapped in the "ok" tag. The exact shape of the returned data is left undefined to allow for flexibility in various Task types. ```json -{ "ok": 42 } +{"ok": 42} ``` -#### 4.2.2.1.3 Failure +## 5.2.2 Failure -The failure branch MAY contain detail about why execution failed wrapped in the `"error"` tag. The exact shape of the returned data is left undefined to allow for flexibility. +The failure branch MAY contain detail about why execution failed wrapped in the "error" tag. It is left undefined in this specification to allow for [Task] types to standardize the data that makes sense in their contexts. If no information is available, this field SHOULD be set to `{}`. @@ -702,76 +920,128 @@ If no information is available, this field SHOULD be set to `{}`. } ``` -### 4.2.2.2 Extended Result +# 6 Receipt + +An [Invocation] Receipt is an attestation of the [Result] of an Invocation. A Receipt MUST be signed by the [Executor] or it's delegate. If signed by delegate, proof of delegation from Executor to the Issuer (the `iss` of the receipt) MUST be provided in `prf`. + +**NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. + +Receipts MUST use the same version as the invocation that they contain. -An extension of Result records SHOULD be used when result of [Invocation] does not fit binary success / failure criteria. +## 6.1 Schema ```ipldsch -type Status union { - | T ("ok") # Success - | X ("error") # Failure - | P ("pending") # Pending -} representation keyed -``` +type Receipt struct { + ran &Invocation -```json -{ "pending": {} } + # output of the invocation + out Result + + # Related receipts + origin optional &Receipt + + # All the other metadata + meta { String: Any } (implicit {}) + + # Principal that issued this receipt. If omitted issuer is + # inferred from the invocation task audience. + iss optional Principal + + # When issuer is different from executor this MUST hold a UCAN + # delegation chain from executor to the issuer. Should be omitted executor is an issuer. + prf [&UCAN] implicit ([]) + + # Signature from the `iss`. + s Varsig +} ``` -### 4.2.3 Recursive Receipt +## 6.2 Fields + +### 6.2.1 Invocation + +The `ran` field MUST include a link to the Invocation that the Receipt is for. -In the case when Invocation execution is delimited it MAY produce multiple states that SHOULD be chained by `origin` field. +### 6.2.2 Output -### 4.2.4 Metadata Fields +The `out` field MUST contain the output of the invocation in [Result] format. + +### 6.2.3 Linked Receipts + +In the case when [Invocation] execution is delimited it MAY produce multiple receipts, which SHOULD be chained by `origin` field. + +### 6.2.4 Metadata Fields The metadata field MAY be omitted or used to contain additional data about the receipt. This field MAY be used for tags, commentary, trace information, and so on. -### 4.2.5 Receipt Issuer +### 6.2.5 Receipt Issuer + +The OPTIONAL `iss` field, if present MUST contain the signer of the receipt. It MUST be an [Executor] or it's delegate. If delegate proof of delegation MUST be provided in `prf` field. + +If `iss` field is omitted, it MUST implicitly imply an [Executor]. + +### 6.2.6 Proofs -The `iss` field MUST contain the signer of the receipt. It MAY be an `aud` of the Invocation or it's delegate. In later case proof delegating invoked capability MUST be provided. -### 4.2.6 Proofs +The `prf` field MUST contain links to UCAN(s) that that delegate authority to perform the invocation from the [Executor] to the Receipt issuer (`iss`). If [Executor] and the Issuer are same no proofs are required. -The `prf` field MUST contain links to any UCANs that delegate authority to perform the invocation from the Executor to the receipt issuer (`iss`). If Executor and the receipt issuer are same no proofs are required. +### 6.2.7 Signature -### 4.2.7 Signature +The `s` field MUST contain a [Varsig] of the [DAG-CBOR] encoded Receipt without `s` field. The signature MUST be generated by the issuer (`iss`). -The `s` field MUST contain a [Varsig] of the receipt payload, a receipt without `s` fields encoded in [DAG-CBOR]. The signature MUST be generated by the `iss`. +## 6.3 DAG-JSON Examples -## 4.3 DAG-JSON Examples +### 6.3.1 Issued by Executor ```json { - "job": { "/": "bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e" }, + "ran": { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, + "meta": { + "time": [400, "hours"], + "retries": 2 + }, "out": { - "ok": [ - { - "from": "bob@example.com", - "text": "Hello world!" - }, - { - "from": "carol@example.com", - "text": "What's up?" - } - ] + "ok": { + "from": "bob@example.com", + "text": "Hello world!" + } + }, + "s": { + "/": { + "bytes": "7aEDQHLoLYWZT7LC8EivDB6YRTlFS3WUlhwIuoxBImNqSqE3Gh2sMvFdT75zZp2w6iN9HfosCguYI4RPZ8Ia1NFRRAo" + } + } +} +``` + +### 6.3.2 Issued by Delegate + +```json +{ + "ran": { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, + "out": { + "ok": { + "from": "bob@example.com", + "text": "Hello world!" + } }, "meta": { "time": [400, "hours"], "retries": 2 }, - "iss": "did:web:ucan.run", + "iss": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z", "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } + { "/": "bafyreibyuz4gwkrr3f7m6duhlm752u22zhk3bb5bjv4c6jwqahbd4jk2gy" } ], - "sig": { + "s": { "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt_VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" + "bytes": "7aEDQI74mv2/6E4P7F3MxgcpORg/kVIhhU1y69WQm1Fp7AD4ScL9lxRc4n+vEPtPzi6NC6we9Lv7137AezohWZyVSQs" } } } ``` -# 5 Promise +# 7 Promise > Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips > @@ -787,89 +1057,45 @@ For example, consider the following invocation batch: ```json { - "v": "0.1.0", - - "with": "mailto:akiko@example.com", - "do": "msg/send", - - "inputs": { - "to": { - "<-": [ - { "/": "bafkreidcqdxosqave5u5pml3pyikiglozyscgqikvb6foppobtk3hwkjn4" }, - "ok" - ] - }, - "subject": "Coffee", - "body": { - "<-": [ - { "/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu" }, - "ok" - ] - } - }, - - "prf": [ - { "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" } - ], - "auth": { "/": "bafy...auth" } -} -``` - -Which is roughly equivalent of the of the following invocation, which inlines above linked invocations instead for illustration purposes. - -> ℹ️ In most cases invocation batches are likely to be [CAR] encoded to avoid running into [block size limitation](https://discuss.ipfs.tech/t/git-on-ipfs-links-and-references/730/2) making inlined variants less common. - -```json -{ - "bafy...atuh": { - "v": "0.1.0", - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "run": [ - { "/": "bafy...mkDraft" }, - { "/": "bafy...edtrs" }, - { "/": "bafy...msgSend" } - ], - "s": { - "/": { - "bytes:": "5vNn4--uTeGk_vayyPuNTYJ71Yr2nWkc6AkTv1QPWSgetpsu8SHegWoDakPVTdxkWb6nhVKAz6JdpgnjABppC7" - } - } - }, - "bafy...mkDraft": { - "meta": { "name": "create-draf" }, - "v": "0.1.0", + "bafyreih47gncsxq7ykxvfiecqdnwi566siv7rsv5iggyygq5eltvyxbz2y": { "with": "https://example.com/blog/posts", "do": "crud/create", "input": { "payload": { - "title": "How UCAN Tasks Changed My Life", - "body": "This is the story of how one spec changed everything..." + "body": "This is the story of how one spec changed everything...", + "title": "How UCAN Tasks Changed My Life" } }, - "prf": [{"/": "bafkreieimb4hvcwizp74vu4xfk34oivbdojzqrbpg2y3vcboqy5hwblmeu"}], - "auth": {"/": "bafy...auth"} + "prf": [ + { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } + ] }, - "bafy...edtrs": { - "meta": { "name": "get-editors" }, - "v": "0.1.0", + "bafyreidgaki3ds2qqus4xz62lhjpycf5dy6p3kiyfurnesrlziucqglrya": { "with": "https://example.com/users/editors", "do": "crud/read", - "prf": [{"/": "bafybeibwlfwol5bdwj75hdqs3liv6z7dyqwtd2t4ovvgncv4ixkaxlkfle"}], - "auth": {"/": "bafy...auth"} + "prf": [ + { "/": "bafyreie3ukg4h2kf7lnx7k62kjujlo2a5l66rh7e7vlj52fnsrj7tuc2ya" } + ] }, - "bafy...msgSend": { - "v": "0.1.0", + "bafyreibcrmrmpw7igdo42samzveqde7kb4zggg6xpde5uzkhn7w4i77eum": { "with": "mailto:akiko@example.com", "do": "msg/send", "input": { - "to": { "<-": [{ "/": "bafy...mkDraft" }, "ok"] }, + "body": { + "await/task/ok": { + "/": "bafyreih47gncsxq7ykxvfiecqdnwi566siv7rsv5iggyygq5eltvyxbz2y" + } + }, "subject": "Coffee", - "body": { "<-": [{ "/": "bafy...edtrs" }, "ok"] } + "to": { + "await/task/ok": { + "/": "bafyreidgaki3ds2qqus4xz62lhjpycf5dy6p3kiyfurnesrlziucqglrya" + } + } }, - "prf": [{ "/": "bafkreie2cyfsaqv5jjy2gadr7mmupmearkvcg7llybfdd7b6fvzzmhazuy" }] - ], - "auth": {"/": "bafy...auth"} + "prf": [ + { "/": "bafyreihvee5irbkfxspsim5s2zk2onb7hictmpbf5lne2nvq6xanmbm6e4" } + ] } } ``` @@ -882,64 +1108,62 @@ const createDraft = crud.create("https://example.com/blog/posts", { title: "How UCAN Tasks Changed My Life", body: "This is the story of how one spec changed everything...", }, +}, { + proofs: [CID.parse('bafyreidxwlsckxfpyy4safgdyqq4lhlyxsreppfcfbh2373w53rslqb7ly')] }) -const getEditors = crud.read("https://example.com/users/editors") +const getEditors = crud.read("https://example.com/users/editors", {}, { + proofs: [CID.parse('bafyreifytuitcmz47m63gibhfgzngzbjo5c6bfklxesgntplol3b7fek4i')] +}) const notify = msg.send("mailto:akiko@example.com", { - to: await createDraft, + to: (await createDraft).ok, subject: "Coffee", - body: await getEditors, + body: (await getEditors).ok, +}, { + proofs: [CID.parse('bafyreibukcml2eggjterlo4zxxal4livmm2dou4jeizgwxnclqbvkbsrle')] }) ``` -While a Promise MAY be substituted for any field in an Invocation, substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. +While a Promise MAY be substituted for any field in a [Task], substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. After resolution, the Invocation MUST be validated against the UCANs known to the Executor. A Promise resolved to an invocation that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. -Promises MAY be used inside of a single Invocation, or across multiple Invocations, and MAY even be across multiple Invokers and Executors. As long as the pointer can be resolved, any invocation MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). +Promises MAY be used inside of a single Invocation, or across multiple Invocations, and MAY even be across multiple Invokers and Executors. As long as the invocation can be resolved, it MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). -A Promise SHOULD resolve to a [Result] or it's [extension][extended result]. If a particular branch's value is required to be unwrapped, the Result tag (`ok` or `error`) MAY be supplied as a selector. +A Promise SHOULD resolve to a [Result]. If a particular branch's value is required to be unwrapped, the Result branch (ok or error) MAY be specified. -An invocation MUST fail if promise is resolved does not match the supplied branch selector. +An invocation MUST fail if promise is resolved does not match the specified branch. -## 5.1 Schema +## 7.1 Schema -The `Promise` describes a pointer to the eventual value in a Promise. When the `selector` of the `Await` omitted pointer resolves to the invocation result (`out` of the Receipt), otherwise it resolves to the specified branch `ok` on success and `error` on failure. Selector could also be used to resolve value nested deeper in any branch. +The `Promise` describes a pointer to the eventual value in a Promise on either branch (`await/task/*`, `await/invocation/*`), or specifically the success (`await/task/ok`, `await/invocation/ok`) or failure (`await/task/error`, `await/invocation/error`) branches. ```ipldsch -# Promise is a way to reference data from the output of the invocation -type Promise struct { - Await "<-" +type Promise union { + &Invocation "await/invocation/*" + &Invocation "await/invocation/ok" + &Invocation "await/invocation/error" + # If it is a task you MUST derive invocation by using authorization of + # this invocation + &Task "await/task/*" + &Task "await/task/ok" + &Task "await/task/error" } representation keyed +``` +## 7.2 Variants -type Await union { - # Invocation reference - | &Invocation - # Inline invocation - | Invocation - # Specific invocation output - | ResultSelector -} representation kinded +### 7.2.1 Relative Addressing -type ResultSelector struct { - job InvocationReference - at Selector -} representation tuple +Promises keyed as `await/task/*`, `await/task/ok`, `await/task/error` use a `Task` link to reference an Invocation in the same [Authorization]. They MUST be resolved to `Invocation` with the same [Authorization] as the invocation using a promise. -type Selector union { - | Key String - | Path [String] -} kinded +> Note that [Task]s in the same [Authorization] will be unable to use `Invoaction` link because it would be impossible to sign an authorization and reference it from the [Task] linked from the authorization. -type InvocationReference union { - | Invocation - | &Invocation -} representation kinded -``` +### 7.2.2 + +Promises keyd as `await/invocation/*`, `await/invocation/ok`, `await/invocation/error` use an [Invocation] when referencing an Invocation from different [Authorization]. -If there are dependencies or ordering required, then you need a promise pipeline ## 5.2 Pipelines @@ -967,80 +1191,113 @@ flowchart BR ```json { - "bafy...auth": { - "iss": "did:key:z6Mkqa4oY9Z5Pf5tUcjLHLUsDjKwMC95HGXdE1j22jkbhz6r", - "aud": "did:web:ucan.run", - "run": [ - { "/": "bady...crudUp" }, - { "/": "bady...dns" }, - { "/": "bafy...bob" }, - { "/": "bafy...carol" }, - { "/": "bafy...log" } - ], - "sig": { - "/": { - "bytes": "bdNVZn_uTrQ8bgq5LocO2y3gqIyuEtvYWRUH9YT-SRK6v_SX8bjt-VZ9JIPVTdxkWb6nhVKBt6JGpgnjABpOCA" - } - } - }, - "bafy...dns": { - "v": "0.1.0", - "with": "dns:example.com?TYPE=TXT", - "do": "crud/update", - "input": { "value": "hello world" }, - "prf": [ - { "/": "bafyreicelfj3kxtnpp2kwefs66rebbnpawjjnvkscrdtyjc6bxjmuix27u" } - ], - "auth": { "/": "bafy...auth" } - }, - "bafy...bob": { - "v": "0.1.0", - "with": "mailto://alice@example.com", - "do": "msg/send", - "input": { - "to": "bob@example.com", - "subject": "DNSLink for example.com", - "body": { "<-": [{ "/": "bafy...dns" }, "ok"] } - }, - "prf": [ - { "/": "bafyreialservj7dxazg4cskm5fuqwh5atgs54rgkvpmtkylbddygs37tce" } - ], - "auth": { "/": "bafy...auth" } - }, - "bafy...carol": { - "v": "0.1.0", - "with": "mailto://alice@example.com", - "do": "msg/send", - "input": { - "to": "carol@example.com", - "subject": "Hey Carol, DNSLink was updated!", - "body": { "<-": [{ "/": "bafy...dns" }, "ok"] } + "blocks": { + "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla": { + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "input": { + "value": "hello world" + }, + "prf": [ + { "/": "bafyreieynwqrabzdhgl652ftsk4mlphcj3bxchkj2aw5eb6dc2wxieilau" } + ] }, - "prf": [ - { "/": "bafyreifusp3qabhrzexltt6ausy4cz7t3cjxcnmwyiqkon5iuthx4h5uo4" } - ], - "auth": { "/": "bafy...auth" } - }, - "bafy...log": { - "v": "0.1.0", - "with": "https://example.com/report", - "do": "crud/update", - "input": { - "payload": { - "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com", "carol@example.com"], - "event": "email-notification" + "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": "bob@example.com", + "subject": "DNSLink for example.com", + "body": { + "await/task/ok": { + "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" + } + } }, - "_": [ - { "<-": [{ "/": "bafy...bob" }, "ok"] }, - { "<-": [{ "/": "bafy...carol" }, "ok"] } + "prf": [ + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } ] }, - "prf": [ - { "/": "bafyreihwfiwuv4f2sajj7r247rezqaarhydd7ffod4tcesdv2so5nkmq7y" } + "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq": { + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": "carol@example.com", + "subject": "Hey Carol, DNSLink was updated!", + "body": { + "await/task/ok": { + "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" + } + } + }, + "prf": [ + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } + ], + }, + "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um": { + "with": "https://example.com/report", + "do": "crud/update", + "input": { + "payload": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification" + }, + "_": [ + { + "await/task/ok": { + "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" + } + }, + { + "await/task/ok": { + "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" + } + } + ] + }, + "prf": [ + { "/": "bafyreiflsrhtwctat4gulwg5g55evudlrnsqa2etnorzrn2tsl2kv2in5i" } + ], + }, + "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4": { + "v": "0.1.0", + "run": [ + { "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" }, + { "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" }, + { "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" }, + { "/": "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um" } + ], + "nnc": "abcdef", + "s": { + "/": { + "bytes": "7aEDQIcE6PG2nwLfi3zE/iT7scpYXKBCu5bfTVwIRFNCpAKpBnMp6eEHgsFg3mwDkX/BKpWdiWaQymNyTYQ+HXYNEgM" + } + } + }, + "bafyreih5lzopso4jtwq7v2mt33e5p2ihuvqxmsmpiz34z7ptc5myraxara": [ + { "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" }, + { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } ], - "auth": { "/": "bafy...auth" } - } + "bafyreiawyrlpqzex2xabvghi7eiuuy47ida5tfkwjugsgh4mc4tuaeteja": [ + { "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" }, + { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + ], + "bafyreidwsezjbi5taeedhyn6n3i7hlmbkglvgtpim67e4wrm6iolprurse": [ + { "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" }, + { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + ], + "bafyreiby75y3vpappphkarojt5gr4oxwviqn3oor57q6q3l2yqmqjl4mcu": [ + { "/": "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um" }, + { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + ] + }, + "roots": [ + { "/": "bafyreih5lzopso4jtwq7v2mt33e5p2ihuvqxmsmpiz34z7ptc5myraxara" }, + { "/": "bafyreiawyrlpqzex2xabvghi7eiuuy47ida5tfkwjugsgh4mc4tuaeteja" }, + { "/": "bafyreidwsezjbi5taeedhyn6n3i7hlmbkglvgtpim67e4wrm6iolprurse" }, + { "/": "bafyreiby75y3vpappphkarojt5gr4oxwviqn3oor57q6q3l2yqmqjl4mcu" } + ] } ``` @@ -1088,8 +1345,12 @@ Thanks to [Rod Vagg](https://github.com/rvagg/) for the clarifications on IPLD S [result]: #4221-Result [extended result]: #4222-Extended-Result [pipelines]: #52-Pipelines -[invocation]: #3-Invocation -[receipt]: #4-receipt -[promise]: #5-promise +[task]: #3-Task +[authorization]: #4-Authorization +[invocation]: #4-Invocation +[receipt]: #5-receipt +[promise]: #6-promise [executor]: #323-executor [invoker]: #322-invoker +[ipld representation]:https://ipld.io/docs/schemas/features/representation-strategies/ +[lazy-vs-eager]: #112-Lazy-vs-Eager-Evaluation From c9296058869c84e76c1c4be662a584c60144e42a Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Sun, 29 Jan 2023 23:12:03 -0800 Subject: [PATCH 14/16] Apply suggestions from code review --- invocation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invocation.md b/invocation.md index ebc0cdb..dd8e6f8 100644 --- a/invocation.md +++ b/invocation.md @@ -751,7 +751,7 @@ The `sig` field MUST contain a [Varsig] of the [CBOR] encoded `Authorization` bl } ``` -### 3.4.2 With all the optionals +### 3.4.2 With all the optional fields ```json { @@ -782,9 +782,9 @@ The `sig` field MUST contain a [Varsig] of the [CBOR] encoded `Authorization` bl As [noted in the introduction][lazy-vs-eager], there is a difference between a reference to a function and calling that function. [Tasks] are not executable until they have been authorized using signed [Authorization] from the [Invoker]. -Invocation describes `(task, authorization)` tuple and which instructs [Executor] to run the [Task]. +Invocation describes `(task, authorization)` tuple, which instructs [Executor] to run the [Task]. -The `authorization` MUST authorize the `task` - Linked [Authorization] MUST contain `task` in it's `run` field. +The `authorization` MUST authorize the `task`. Linked [Authorization] MUST contain `task` in it's `run` field. ## 4.1 Schema @@ -1162,7 +1162,7 @@ Promises keyed as `await/task/*`, `await/task/ok`, `await/task/error` use a `Tas ### 7.2.2 -Promises keyd as `await/invocation/*`, `await/invocation/ok`, `await/invocation/error` use an [Invocation] when referencing an Invocation from different [Authorization]. +Promises keyed as `await/invocation/*`, `await/invocation/ok`, `await/invocation/error` use an [Invocation] when referencing an Invocation from different [Authorization]. ## 5.2 Pipelines From d4f9bf0fe9ca2671ae9f843ede53b0874963e106 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Sun, 29 Jan 2023 23:12:20 -0800 Subject: [PATCH 15/16] ignore ok --- .github/workflows/words-to-ignore.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/words-to-ignore.txt b/.github/workflows/words-to-ignore.txt index d803832..3892578 100644 --- a/.github/workflows/words-to-ignore.txt +++ b/.github/workflows/words-to-ignore.txt @@ -74,6 +74,7 @@ canonicalized OAuth simple-but-evolvable implicits +ok # actually correct reimagine From 38ea3cf2350f4083dff1b9a9e269975820cafa5c Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Mon, 30 Jan 2023 23:56:55 -0800 Subject: [PATCH 16/16] update spec per consensus --- invocation.md | 942 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 596 insertions(+), 346 deletions(-) diff --git a/invocation.md b/invocation.md index dd8e6f8..ae27445 100644 --- a/invocation.md +++ b/invocation.md @@ -1,4 +1,4 @@ -# UCAN Execution Specification v0.1.0 +# UCAN Invocation Specification v0.1.0 ## Editors @@ -21,8 +21,6 @@ UCAN Invocation defines a format for expressing the intention to execute delegated UCAN capabilities, the attested receipts from an execution, and how to extend computation via promise pipelining. -> This is based on [UCAN invocation] specification, altering it slightly in order to make tasks self-contained as discussed in [#6](https://github.com/ucan-wg/invocation/issues/6) - ## Language The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). @@ -172,13 +170,13 @@ Task adds two new roles to UCAN: invoker and executor. The existing UCAN delegat The invoker signals to the executor that a task associated with a UCAN SHOULD be performed. -The invoker MAY be specified explicitly using `iss` field. If `iss` field is omitted invoker is specified implicitly using `iss` field of the contained UCAN authorizing the invocation. +The invoker MUST be the UCAN delegator. Their DID MUST be authenticated in the `iss` field of the contained UCAN. ### 2.1.2 Executor The executor is directed to perform some task described in the UCAN by the invoker. -The executor MAY be specified explicitly using `aud` field. If `aud` field is omitted executor is specified implicitly using `aud` field of the contained UCAN authorizing the invocation. +The executor MUST be the UCAN delegate. Their DID MUST be set the in `aud` field of the contained UCAN. ## 2.2 Components @@ -190,7 +188,8 @@ auth: Authorization send: Invocation send.task: Task -send.promise: Promise +send.promise: Await +read.promise: Await send.receipt: Receipt read: Invocation @@ -199,25 +198,11 @@ read.task: Task update: Invocation update.task: Task -send.proof: UCAN - state auth { - direction TB - - state run <> - - sig-->iss: iss - sig-->aud: aud - sig-->meta: meta - sig-->nnc: nnc - sig-->run: run - + direction LR + s-->scope - iss: did꞉key꞉z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi - aud: did꞉web꞉ucan.run - meta: {} - nnc: "some nonce" } state send.task { @@ -227,16 +212,22 @@ state send.task { send.task.do: do send.task.with: with send.task.input: input + send.task.nnc: nnc + send.task.meta: meta + send.do: msg/send send.with: mailto꞉//alice@example.com send.input: {"to"꞉ "bob@example.com", "subject"꞉ "DNSLink for example.com"} send.prf: UCAN + send.task.meta.data: {tags꞉ ["demo"]} send.task.do --> send.do send.task.with --> send.with send.task.input --> send.input send.task.prf --> send.prf + send.task.nnc --> "nonce" + send.task.meta --> send.task.meta.data } state read.task { @@ -301,10 +292,15 @@ state send.promise { state send.promise.await <> } +state read.promise { + state read.promise.await <> +} + state send.receipt { send.receipt.sig: sig send.receipt.ran: ran - send.receipt.out: result + send.receipt.out: out + send.receipt.fx: fx send.receipt.meta: meta send.receipt.iss: iss send.receipt.prf: prf @@ -328,19 +324,24 @@ read.auth --> auth update.run --> update.task update.auth --> auth -run --> send.task -run --> read.task -run --> update.task +scope --> send.task +scope --> read.task +scope --> update.task send.promise.await --> send: ok send.promise.await --> send: error send.promise.await --> send: * +read.promise.await --> read.task: ok +read.promise.await --> read.task: error +read.promise.await --> read.task: * + send.receipt.out --> send.result send.receipt.sig --> send.receipt.ran send.receipt.sig --> send.receipt.out +send.receipt.sig --> send.receipt.fx send.receipt.sig --> send.receipt.meta send.receipt.sig --> send.receipt.iss send.receipt.sig --> send.receipt.prf @@ -377,29 +378,25 @@ A [promise] is a reference to an eventual [Receipt] of an [Invocation]. ```ipldsch type Task struct { + v SemVer + with URI do Ability input In (implicit {}) meta {String : Any} (implicit {}) + nnc string (implicit "") prf [&UCAN] (implicit []) } -type Authorization struct { - v SemVer - - # CIDs MUST be sorted alphabetically - run [&Task] (implicit []) - - # If omitted iss / aud MUST be derived from the - # delegated proof(s) - iss optional Principal - aud optional Principal - - meta {String : Any} (implicit {}) - nnc string (implicit "") +type SemVer string +type URI string +type Authorization struct { + # Authorization is denoted by the set of links been authorized + scope [&Any] (implicit []) + # Scope signed by the invoker s VarSig } @@ -413,6 +410,8 @@ type Receipt struct { # output of the invocation out Result + # Effects to be performed + fx [&Invocation] (implicit {}) # Related receipts origin optional &Receipt @@ -435,40 +434,36 @@ type Receipt struct { type Result union { | Ok ("ok") # Success | Error ("error") # Error -} +} representation kinded -# Promise is a way to reference invocation receipt -type Promise union { - &Invocation "await/invocation/*" - &Invocation "await/invocation/ok" - &Invocation "await/invocation/error" - # If it is a task you MUST derive invocation by using authorization of - # this invocation - &Task "await/task/*" - &Task "await/task/ok" - &Task "await/task/error" +type Promise union { + | &Task ("ucan/task") + | &Invocation ("ucan/invocation") } representation keyed -type URI string +# Promise is a way to reference invocation receipt +type Await union { + &Promise "await/*" + &Promise "await/ok" + &Promise "await/error" +} representation keyed ``` # 3 Task -A Task is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, input)` triple. The `input` field is free form, and depend on the specific resource and ability being interacted with, and not described in this specification. +A Task is the smallest unit of work that can be requested from a UCAN. It describes one `(resource, ability, input)` triple. The `input` field is free form, and depend on the specific resource and ability being interacted with, and is not described in this specification. Using the JavaScript analogy from the introduction, a Task is similar to wrapping a call in an anonymous function: ```json { + "v": "0.1.0", "with": "mailto://alice@example.com", "do": "msg/send", "input": { - "body": "world", + "to": ["bob@example.com", "carol@example.com"], "subject": "hello", - "to": [ - "bob@example.com", - "carol@example.com" - ] + "body": "world" }, "prf": [ { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } @@ -482,10 +477,6 @@ Using the JavaScript analogy from the introduction, a Task is similar to wrappin to: ["bob@example.com", "carol@example.com"], subject: "hello", body: "world" -}, { - proofs: [ - CID.parse('bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha') - ] }) ``` @@ -493,24 +484,28 @@ Later, when we explore [Promise]s, this also includes capturing the promise: ```json { - "bafyreibfcowm533mjovwymxu5wqg7tiqddvez5atgocyyomll3ouykppfy": { - "with": "https://example.com/mailinglist", + "bafyreihroupaenuxjduzs4ynympv3uawcct4mj4sa7ciuqqlss4httehiu": { + "v": "0.1.0", "do": "crud/read", + "with": "https://example.com/mailinglist", "prf": [ { "/": "bafyreib2dldcev74g5i76bacd4w72gowuraouv2kvnwa2ympvrvtybcsfi" } ] }, - "bafyreif3k6kz4pip7wu6j26kdrrju3tbttcqhcmibdy6rpolazv6vds2kq": { + "bafyreigiz22kfo4bbrsl3jyspm5ykuh2jk5hxmduc5yzijp3dzegvvi6tq": { + "v": "0.1.0", "with": "mailto://alice@example.com", "do": "msg/send", "input": { - "body": "world", - "subject": "hello", "to": { - "await/task/*": { - "/": "bafyreibfcowm533mjovwymxu5wqg7tiqddvez5atgocyyomll3ouykppfy" + "await/*": { + "ucan/task": { + "/": "bafyreihroupaenuxjduzs4ynympv3uawcct4mj4sa7ciuqqlss4httehiu" + } } - } + }, + "subject": "hello", + "body": "world" }, "prf": [ { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } @@ -521,49 +516,49 @@ Later, when we explore [Promise]s, this also includes capturing the promise: ```js // Pseudocode -const mailingList = crud.read("https://exmaple.com/mailinglist", {}, { - proofs: [ - CID.parse('bafyreib2dldcev74g5i76bacd4w72gowuraouv2kvnwa2ympvrvtybcsfi') - ] -}) +const mailingList = crud.read("https://exmaple.com/mailinglist") msg.send("mailto:alice@example.com", { to: (await mailingList).ok, subject: "hello", body: "world", -}, { - proofs: [ - CID.parse('bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha') - ] }) ``` ## 3.1 Schema ```ipldsch -type Task struct { +type Task struct { + v SemVer + with URI do Ability - input {String: Any} (implicit {}) + input In (implicit {}) meta {String : Any} (implicit {}) + nnc string (implicit "") prf [&UCAN] (implicit []) } +type SemVer string type URI string ``` ## 3.2 Fields -### 3.2.1 Resource +### 3.2.1 UCAN Task Version + +The `v` field MUST contain the SemVer-formatted version of the UCAN Task Specification that this struct conforms to. + +### 3.2.2 Resource The `with` field MUST contain the [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) of the resource being accessed. If the resource being accessed is some static data, it is RECOMMENDED to reference it by the [`data`](https://en.wikipedia.org/wiki/Data_URI_scheme), [`ipfs`](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls), or [`magnet`](https://en.wikipedia.org/wiki/Magnet_URI_scheme) URI schemes. -### 3.2.2 Ability +### 3.2.3 Ability The `do` field MUST contain a [UCAN Ability](https://github.com/ucan-wg/spec/#23-ability). This field can be thought of as the message or trait being sent to the resource. -### 3.2.3 Input +### 3.2.4 Input The OPTIONAL `input` field, MAY contain any parameters expected by the URI/Ability pair, which MAY be different between different URIs and Abilities, and is thus left to the executor to define the shape of this data. @@ -579,19 +574,21 @@ UCAN capabilities provided in [Proofs] MAY impose certain constraint on the type If `input` field is not present, it is implicitly a `unit` represented as empty map. -### 3.2.4 Metadata + +### 3.2.5 Metadata The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. If `meta` field is not present, it is implicitly a `unit` represented as an empty map. +### 3.2.6 Nonce + +If present, the OPTIONAL `nnc` field MUST include a random nonce expressed in ASCII. This field ensures that multiple invocations are unique. -### 3.2.5 Proofs -The `prf` field MUST contain links to any UCANs that provide the authority to perform the task. All of the outermost proofs MUST either +### 3.2.7 Proofs -1. Set `aud` fields to the [Executor]'s DID and `iss` field set to the [Invoker]'s DID, allowing Executor to (re)delegate enclosed capabilities. -2. Set `aud` field to the [Invoker]'s DID, preventing Executor from (re)delegating enclosed capabilities. +The `prf` field MUST contain links to any UCANs that provide the authority to perform this task. All of the outermost proofs MUST have `aud` field set to the [Executor]'s DID. All of the outmost proofs MUST have `iss` field set to the [Invoker]'s DID. ## 3.3 DAG-JSON Examples @@ -600,6 +597,7 @@ The `prf` field MUST contain links to any UCANs that provide the authority to pe ```json { + "v": "0.1.0", "with": "https://example.com/blog/posts", "do": "crud/create", "input": { @@ -622,7 +620,8 @@ The `prf` field MUST contain links to any UCANs that provide the authority to pe ### 3.3.2 Sending Email ```json -{ +`{ + "v": "0.1.0", "with": "mailto:akiko@example.com", "do": "msg/send", "input": { @@ -644,6 +643,7 @@ The `prf` field MUST contain links to any UCANs that provide the authority to pe ```json { + "v": "0.1.0", "with": "data:application/wasm;base64,AHdhc21lci11bml2ZXJzYWwAAAAAAOAEAAAAAAAAAAD9e7+p/QMAkSAEABH9e8GowANf1uz///8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAACAAAACoAAAAIAAAABAAAACsAAAAMAAAACAAAANz///8AAAAA1P///wMAAAAlAAAALAAAAAAAAAAUAAAA/Xu/qf0DAJHzDx/44wMBqvMDAqphAkC5YAA/1mACALnzB0H4/XvBqMADX9bU////LAAAAAAAAAAAAAAAAAAAAAAAAAAvVXNlcnMvZXhwZWRlL0Rlc2t0b3AvdGVzdC53YXQAAGFkZF9vbmUHAAAAAAAAAAAAAAAAYWRkX29uZV9mAAAADAAAAAAAAAABAAAAAAAAAAkAAADk////AAAAAPz///8BAAAA9f///wEAAAAAAAAAAQAAAB4AAACM////pP///wAAAACc////AQAAAAAAAAAAAAAAnP///wAAAAAAAAAAlP7//wAAAACM/v//iP///wAAAAABAAAAiP///6D///8BAAAAqP///wEAAACk////AAAAAJz///8AAAAAlP///wAAAACM////AAAAAIT///8AAAAAAAAAAAAAAAAAAAAAAAAAAET+//8BAAAAWP7//wEAAABY/v//AQAAAID+//8BAAAAxP7//wEAAADU/v//AAAAAMz+//8AAAAAxP7//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU////pP///wAAAAAAAQEBAQAAAAAAAACQ////AAAAAIj///8AAAAAAAAAAAAAAADQAQAAAAAAAA==", "do": "wasm/run", "input": { @@ -659,134 +659,58 @@ The `prf` field MUST contain links to any UCANs that provide the authority to pe # 4 Authorization -An [Authorization] authorizes one or more Tasks to be run. There are a few invariants that MUST hold between the `run`, `iss`, `aud` and `sig` fields: - -- All of the `run` Tasks MUST be provably authorized by the UCANs in their `prf` field. -- All of the `run` Tasks MUST be authorized by the same [Invoker]. -- The [Executor] MUST be either explicitly specified using `aud` field or be inferred per task basis from the audience (`aud`) of the UCANs in their `prf` field. -- The [Invoker] MUST be either explicitly specified using `iss` field, or be inferred from the `run` Tasks by the issuer of the UCANs in their `prf` filed. -- The `sig` field MUST be produced by the [Invoker]. - +An [Authorization] is cryptographically signed data set. It represents an authorization to run [Task]s in that are included in `scope` data set. ## 4.1 Schema ```ipldsch type Authorization struct { - v SemVer - - # CIDs MUST be sorted alphabetically - run [&Task] (implicit []) - - # If omitted iss / aud MUST be derived from the - # delegated proof(s) - iss optional Principal - aud optional Principal - - meta {String : Any} (implicit {}) - nnc string (implicit "") - + # Authorization is denoted by the set of links been authorized + scope [&Any] (implicit []) + # Scope signed by the invoker s VarSig } - -type SemVer string ``` ### 4.2 Fields -### 4.2.1 UCAN Invocation Version - -The `v` field MUST contain the SemVer-formatted version of the UCAN Invocation Specification that this struct conforms to. - -#### 4.2.2 -The `run` field MUST be a set of [Task] links been authorized. It SHOULD be encoded as an alphabetically ordered list without duplicates. +#### 4.2.1 Authorization Scope -If `run` field is omitted, it is implicitly a an empty list and has no practical use as it authorizes no tasks. +The `scope` field MUST be a set of links been authorized. It SHOULD be encoded as an alphabetically ordered list without duplicates. +If `scope` field is omitted, it is implicitly a an empty list and has no practical use as it authorizes nothing. -### 3.2.2 Invoker -If present, the OPTIONAL `iss` field MUST be a principal authorizing task run and MUST be encoded in format describe in [UCAN-IPLD] specification. +### 4.2.2 Signature -If `iss` field is omitted, Invoker MUST be inferred from the tasks by their [Proof]s `iss` field. All of the tasks MUST have a same invoker. +The `s` field MUST contain a [Varsig] of the [CBOR] encoded `scope` field. -### 3.2.3 Executor +## 4.3 DAG-JSON Example -If present, the OPTIONAL `aud` filed MUST be a principal authorized to run the task. It MUST be encoded in format described in [UCAN-IPLD]. - -If `aud` field is omitted, Executor MUST be inferred per task from the [Proof]s `aud` field. - -### 3.3.3 Metadata - -The OPTIONAL `meta` field MAY be used to include human-readable descriptions, tags, execution hints, resource limits, and so on. If present, the `meta` field MUST contain a map with string keys. The contents of the map are left undefined to encourage extensible use. - -Data inside the `meta` field SHOULD NOT be used for [Receipt]s. - -If `meta` field is not present, it is implicitly a `unit` represented as an empty map. - -### 3.3.4 Nonce - -If present, the OPTIONAL `nnc` field MAY include a random nonce expressed in ASCII. This field can ensures that multiple invocations are unique. - - -### 3.3.5 Signature - -The `sig` field MUST contain a [Varsig] of the [CBOR] encoded `Authorization` block without `v` and `s` and `meta` fields. - -## 3.4 DAG-JSON Example - -### 3.4.1 Infer Issuer & Audience ```json { - "v": "0.1.0", - "run": [ - { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, - { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } + "scope": [ + { "/": "bafyreihroupaenuxjduzs4ynympv3uawcct4mj4sa7ciuqqlss4httehiu" }, + { "/": "bafyreigiz22kfo4bbrsl3jyspm5ykuh2jk5hxmduc5yzijp3dzegvvi6tq" } ], "s": { "/": { - "bytes": "7aEDQBIqKMpONi41J/8gYAaKM/yVef4jwQkL4czPlRVid7rLV+Ftk4EgInLHLpLF/xMrJOcnIqQMwSOW3wwJQEzS0wI" - } - } -} -``` - -### 3.4.2 With all the optional fields - -```json -{ - "v": "0.1.0", - "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", - "aud": "did:web:ucan.run", - "run": [ - { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, - { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } - ], - "meta": { - "notes/personal": "I felt like making an invocation today!", - "ipvm/config": { - "time": [5, "minutes"], - "gas": 3000 + "bytes": "7aEDQBzEB/4ViFVHUelItLNcVbXOQEx9rWmfTfoIVnA4QtL08yA3fPcXZd0kRE3Qr0BD61es4R/XHM/yK+kX1PfiWAk" } }, - "nnc": "6c*97-3=", - "s": { - "/": { - "bytes": "7aEDQOQqEXrTp5HgGdYGQYhT+aLDB0IvBGDx161yAoN2EAsF/J0DwPY4j4bJstJX6x9sKy18JzVu1kGSdK10NSaWvAs" - } - } } ``` -# 4 Invocation +# 5 Invocation -As [noted in the introduction][lazy-vs-eager], there is a difference between a reference to a function and calling that function. [Tasks] are not executable until they have been authorized using signed [Authorization] from the [Invoker]. +As [noted in the introduction][lazy-vs-eager], there is a difference between a reference to a function and calling that function. [Tasks] are not executable until they have been authorized by [Invoker] using [Authorization]. -Invocation describes `(task, authorization)` tuple, which instructs [Executor] to run the [Task]. +Invocation describes `(task, authorization)` tuple and instructs [Executor] to run the [Task]. -The `authorization` MUST authorize the `task`. Linked [Authorization] MUST contain `task` in it's `run` field. +The `authorization` MUST authorize the `task` by including link to it in it's `scope` field. The Invocation of the [Task] that is not included in the linked [Authorization] `scope` MUST be considered invalid. -## 4.1 Schema +## 5.1 Schema ```ipldsch type Invocation struct { @@ -795,22 +719,30 @@ type Invocation struct { } representation tuple ``` -## 4.2 Fields +## 5.2 Fields -### 4.2.1 +### 5.2.1 The `task` field MUST contain a link to the [Task] to be run. -### 4.2.2 +### 5.2.2 The `authorization` field MUST contain a link to the [Authorization] that authorizes invoked `task`. -## 4.2 DAG-JSON Example +## 5.3 DAG-JSON Example + +### 5.3.1 Single Invocation + ```json { "blocks": { - "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu": { + "bafyreihwj7mouljcwl7s7xpp6sfexptedrmkraoro2tcgiyu4jnjg2rauu": [ + { "/": "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu" }, + { "/": "bafyreibgo4bq2kxf4ykwz7c4zyvulfytwn46xk5ml2c7tq57fp6d7mhjvq" } + ], + "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu": { + "v": "0.1.0", "with": "https://example.com/blog/posts", "do": "crud/create", "input": { @@ -828,7 +760,57 @@ The `authorization` field MUST contain a link to the [Authorization] that author { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } ] }, - "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi": { + "bafyreibgo4bq2kxf4ykwz7c4zyvulfytwn46xk5ml2c7tq57fp6d7mhjvq": { + "scope": [ + { "/": "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu" } + ], + "s": { + "/": { + "bytes": "7aEDQIUWpsYzEkIX8gjunh81kgGFW9KYlEOewghwmowQRCuhkQsxZmpymmfsXVFSL6m79O1s5c+G2pgqODu2qUM4nAY" + } + } + } + }, + "roots": [ + { "/": "bafyreihwj7mouljcwl7s7xpp6sfexptedrmkraoro2tcgiyu4jnjg2rauu" } + ] +} +``` + +### 5.3.1 Multilpe Invocations + +```json +{ + "blocks": { + "bafyreib527h7rxykieyccwqckfvbrxfwkf6dbiwnilakf7ha3rlofeazdy": [ + { "/": "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu" }, + { "/": "bafyreigdxhwdln62fekut623s5hipnhkgsc7nurtg4utsrlbt6di4dyptm" } + ], + "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu": { + "v": "0.1.0", + "with": "https://example.com/blog/posts", + "do": "crud/create", + "input": { + "headers": { + "content-type": "application/json" + }, + "payload": { + "title": "How UCAN Tasks Changed My Life", + "body": "This is the story of how one spec changed everything...", + "topics": ["authz", "journal"], + "draft": true + } + }, + "prf": [ + { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } + ], + }, + "bafyreiefdnvc5xuakriluhev5gpqephudvkkwhrbo6ixz5cocvi3camsra": [ + { "/": "bafyreicewv7jbynidkzljwc3okytusrljgjpn7xjamrrcnokgp5qei77gy" }, + { "/": "bafyreigdxhwdln62fekut623s5hipnhkgsc7nurtg4utsrlbt6di4dyptm" } + ], + "bafyreicewv7jbynidkzljwc3okytusrljgjpn7xjamrrcnokgp5qei77gy": { + "v": "0.1.0", "with": "mailto:akiko@example.com", "do": "msg/send", "input": { @@ -842,51 +824,32 @@ The `authorization` field MUST contain a link to the [Authorization] that author }, "prf": [ { "/": "bafyreihvee5irbkfxspsim5s2zk2onb7hictmpbf5lne2nvq6xanmbm6e4" } - ] - }, - "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by": { - "v": "0.1.0", - "iss": "did:key:z6Mkk89bC3JrVqKie71YEcc5M1SMVxuCgNx6zLZ8SYJsxALi", - "aud": "did:web:ucan.run", - "run": [ - { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, - { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" } ], - "meta": { - "notes/personal": "I felt like making an invocation today!", - "ipvm/config": { - "gas": 3000, - "time": [5, "minutes"] - } - }, - "nnc": "6c*97-3=", + }, + "bafyreigdxhwdln62fekut623s5hipnhkgsc7nurtg4utsrlbt6di4dyptm": { + "scope": [ + { "/": "bafyreiduxn4l73hs5abjjgpyyazmgar7fd64pdlj62hofr2qm7prnqkwuu" }, + { "/": "bafyreicewv7jbynidkzljwc3okytusrljgjpn7xjamrrcnokgp5qei77gy" } + ] "s": { "/": { - "bytes": "7aEDQOQqEXrTp5HgGdYGQYhT+aLDB0IvBGDx161yAoN2EAsF/J0DwPY4j4bJstJX6x9sKy18JzVu1kGSdK10NSaWvAs" + "bytes": "7aEDQIU6IPBZ8JMaB7mmTAwmIpMR4qMGUdD8R/cU8rc5uzxSQhetKw1dFdePqbffdC6aKdifv5MXO0tA2iKfN7tkhwI" } }, - }, - "bafyreiapsuwuvn5p3logmujov7fphukk7eih73nn5hqb6qlm6vvvf4mpmi": [ - { "/": "bafyreiesce3tetk62gmescvwudyw4w6kf5usfhgad3n255wsneeklty6xu" }, - { "/": "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by" } - ], - "bafyreigu7s6i6av4wztcnkjy5ptf6lxq7vvdujc25r7znpx7xb5wbpw4cy": [ - { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, - { "/": "bafyreidbydkxrasilythrllw6kb5i7lfnrkxsgfabme4smccq37lx5w6by" } - ] + } }, "roots": [ - { "/": "bafyreiapsuwuvn5p3logmujov7fphukk7eih73nn5hqb6qlm6vvvf4mpmi" }, - { "/": "bafyreigu7s6i6av4wztcnkjy5ptf6lxq7vvdujc25r7znpx7xb5wbpw4cy" } + { "/": "bafyreib527h7rxykieyccwqckfvbrxfwkf6dbiwnilakf7ha3rlofeazdy" }, + { "/": "bafyreiefdnvc5xuakriluhev5gpqephudvkkwhrbo6ixz5cocvi3camsra" } ] } ``` -# 5 Result +# 6 Result -A `Result` records the output of a [Task], as well as its success or failure state. +A `Result` records the output of the [Task], as well as its success or failure state. -## 5.1 Schema +## 6.1 Schema ```ipldsch type Result union { @@ -895,17 +858,17 @@ type Result union { } ``` -## 5.2 Variants +## 6.2 Variants -## 5.2.1 Success +## 6.2.1 Success -The success branch MUST contain the value returned from a successful [Task] wrapped in the "ok" tag. The exact shape of the returned data is left undefined to allow for flexibility in various Task types. +The success branch MUST contain the value returned from a successful [Task] wrapped in the `"ok"` tag. The exact shape of the returned data is left undefined to allow for flexibility in various Task types. ```json {"ok": 42} ``` -## 5.2.2 Failure +## 6.2.2 Failure The failure branch MAY contain detail about why execution failed wrapped in the "error" tag. It is left undefined in this specification to allow for [Task] types to standardize the data that makes sense in their contexts. @@ -920,15 +883,15 @@ If no information is available, this field SHOULD be set to `{}`. } ``` -# 6 Receipt +# 7 Receipt -An [Invocation] Receipt is an attestation of the [Result] of an Invocation. A Receipt MUST be signed by the [Executor] or it's delegate. If signed by delegate, proof of delegation from Executor to the Issuer (the `iss` of the receipt) MUST be provided in `prf`. +An [Invocation] Receipt is an attestation of the [Result] of an Invocation. A Receipt MUST be signed by the [Executor] or it's delegate. If signed by delegate, proof of delegation from [Executor] to the Issuer (the `iss` of the receipt) MUST be provided in `prf`. **NB: a Receipt this does not guarantee correctness of the result!** The statement's veracity MUST be only understood as an attestation from the executor. Receipts MUST use the same version as the invocation that they contain. -## 6.1 Schema +## 7.1 Schema ```ipldsch type Receipt struct { @@ -936,6 +899,8 @@ type Receipt struct { # output of the invocation out Result + # Effects to be performed + fx [&Invocation] (implicit {}) # Related receipts origin optional &Receipt @@ -956,19 +921,27 @@ type Receipt struct { } ``` -## 6.2 Fields +## 7.2 Fields -### 6.2.1 Invocation +### 7.2.1 Ran Invocation -The `ran` field MUST include a link to the Invocation that the Receipt is for. +The `ran` field MUST include a link to the [Invocation] that the Receipt is for. -### 6.2.2 Output +### 7.2.2 Output The `out` field MUST contain the output of the invocation in [Result] format. -### 6.2.3 Linked Receipts +### 7.2.3 Effects + +Some [Task]s may describe complex workflows with multiple, sometimes concurrent, steps. Such [Task]s MAY capture each state update using `out` of the chained [Receipt] and consequent (concurrent) steps using `fx` [Invocation]s. + +The OPTIONAL `fx` field, if present MUST contain set of concurrent [Invocation]s that need to be run by the [Executor] to complete execution of the ongoing [Invocation]. [Executor] MUST run contained [Invocation]s concurrently unless they are ordered using promise pipelining. + +If `fx` field is omitted, or if field is set to an empty list it denote an end of the execution. -In the case when [Invocation] execution is delimited it MAY produce multiple receipts, which SHOULD be chained by `origin` field. +### 7.2.4 Linked Receipts + +When [Invocation] consisets of multiple steps, each step MAY produce a receipt, each subsequent one SHOULD be chained with previous receipt using `origin` field. ### 6.2.4 Metadata Fields @@ -995,10 +968,8 @@ The `s` field MUST contain a [Varsig] of the [DAG-CBOR] encoded Receipt without ```json { - "ran": { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, - "meta": { - "time": [400, "hours"], - "retries": 2 + "ran": { + "/": "bafyreibt3t5uzjiwn4b3rszto6ox2z4najv45rzi75dooti3ad2rl7qkoi" }, "out": { "ok": { @@ -1006,9 +977,13 @@ The `s` field MUST contain a [Varsig] of the [DAG-CBOR] encoded Receipt without "text": "Hello world!" } }, + "meta": { + "retries": 2, + "time": [400, "hours"] + }, "s": { "/": { - "bytes": "7aEDQHLoLYWZT7LC8EivDB6YRTlFS3WUlhwIuoxBImNqSqE3Gh2sMvFdT75zZp2w6iN9HfosCguYI4RPZ8Ia1NFRRAo" + "bytes": "7aEDQJBRBQPMyopsMw84vBQa2EdrRZtz9Kk7oF+tZ5eVIKSjeQwy1ReCGVXkt56cEhlOER7uOpIugSwUtf8VapW1TQM" } } } @@ -1018,7 +993,10 @@ The `s` field MUST contain a [Varsig] of the [DAG-CBOR] encoded Receipt without ```json { - "ran": { "/": "bafyreiddsg6i4ypntdnju4gdtqztxqnqf2fty7wsdflxlnos5t75xbxhfi" }, + "ran": { + "/": "bafyreicewv7jbynidkzljwc3okytusrljgjpn7xjamrrcnokgp5qei77gy" + }, + "iss": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z", "out": { "ok": { "from": "bob@example.com", @@ -1026,22 +1004,67 @@ The `s` field MUST contain a [Varsig] of the [DAG-CBOR] encoded Receipt without } }, "meta": { - "time": [400, "hours"], - "retries": 2 + "retries": 2, + "time": [400, "hours"] }, - "iss": "did:key:z6MkrZ1r5XBFZjBU34qyD8fueMbMRkKw17BZaq2ivKFjnz2z", "prf": [ - { "/": "bafyreibyuz4gwkrr3f7m6duhlm752u22zhk3bb5bjv4c6jwqahbd4jk2gy" } + { "/": "bafyreihfgvlol74ugosa5gkzvbsghmq7wiqn4xvgack4uwn4qagrml6p74" } ], "s": { "/": { - "bytes": "7aEDQI74mv2/6E4P7F3MxgcpORg/kVIhhU1y69WQm1Fp7AD4ScL9lxRc4n+vEPtPzi6NC6we9Lv7137AezohWZyVSQs" + "bytes": "7aEDQBAv3B11GOo5Yoa/wMILlK0L3565ainer90OFa0h8XA4awZFKB3bfq0DL00HH+r4tKVq3k440HIG0apYcWppFwI" + } + } +} +``` + +### 6.3.3 Receipts with effects + +```json +{ + "ran": { + "/": "bafyreicrhbr7jjt6pvhldnrl7g6tr4r5uspgea52xo23a2yegfnpspeste" + }, + "out": { + "ok": {} + }, + "fx": [ + { + "/": "bafyreib6mgm5few6pnajc25h6r6trp2kbi6xrmhafnmby3hrflmgql2kna" + } + ], + "s": { + "/": { + "bytes": "7aEDQMO5k6OtYgHSRVIR2sqn97TTfrhLrdTSusoYOAaCV2lg37xI22QMjMhW44eVgkIRcWcbLO4YdrJfcwG4t3jzegM" + } + } +} +``` + +### 6.3.4 Chained Receipt + +```json +{ + "ran": { + "/": "bafyreicrhbr7jjt6pvhldnrl7g6tr4r5uspgea52xo23a2yegfnpspeste" + }, + "out": { + "ok": { + "capacity": "3GiB" + } + }, + "origin": { + "/": "bafyreiga5grjokrjgjn62kbwdf6oz3w5m4hj4ck3ln6k6bw3wguiv2s6oy" + }, + "s": { + "/": { + "bytes": "7aEDQFhBCM0NkHNMpHVn1UWnI90S0hHKMiDbqj4Gf3U3QNRpVM5/Ta9Uy94khq14TREmEWBwkqVMEk/EJ46a4NF33AY" } } } ``` -# 7 Promise +# 8 Pipelines > Machines grow faster and memories grow larger. But the speed of light is constant and New York is not getting any closer to Tokyo. As hardware continues to improve, the latency barrier between distant machines will increasingly dominate the performance of distributed computation. When distributed computational steps require unnecessary round trips, compositions of these steps can cause unnecessary cascading sequences of round trips > @@ -1051,13 +1074,14 @@ There MAY not be enough information to described an Invocation at creation time. Some invocations MAY require input from set of other invocations. Waiting for each request to complete before proceeding to the next task has a performance impact due to the amount of latency. [Promise pipelining](http://erights.org/elib/distrib/pipeline.html) is a solution to this problem: by referencing a prior invocation, a pipelined invocation can direct the executor to use the output of one invocations into the input of the other. This liberates the invoker from waiting for each step. -A Promise MAY be used as a variable placeholder for a concrete value in an [Invocation] output, waiting on a previous step to complete. +A [Promise] / [Await] MAY be used as a variable placeholder for a concrete value in an [Invocation] output, waiting on a previous step to complete. For example, consider the following invocation batch: ```json { - "bafyreih47gncsxq7ykxvfiecqdnwi566siv7rsv5iggyygq5eltvyxbz2y": { + "bafyreif7wiz4mz3ojmmvrz2p5m6ay5nqbi7ghwmheccv5rqgjtkrk5wyi4": { + "v": "0.1.0", "with": "https://example.com/blog/posts", "do": "crud/create", "input": { @@ -1070,26 +1094,32 @@ For example, consider the following invocation batch: { "/": "bafyreid6q7uslc33xqvodeysekliwzs26u5wglas3u4ndlzkelolbt5z3a" } ] }, - "bafyreidgaki3ds2qqus4xz62lhjpycf5dy6p3kiyfurnesrlziucqglrya": { + "bafyreifc2ytuc5dv454a7c6cxk3mm7gt6skoptzj5rtc3ijf65v2bjxssy": { + "v": "0.1.0", "with": "https://example.com/users/editors", "do": "crud/read", "prf": [ { "/": "bafyreie3ukg4h2kf7lnx7k62kjujlo2a5l66rh7e7vlj52fnsrj7tuc2ya" } ] }, - "bafyreibcrmrmpw7igdo42samzveqde7kb4zggg6xpde5uzkhn7w4i77eum": { + "bafyreibodwqz5ghsvf6nmopdbkksp46roc6zakmmd7gv6flymc2edeeq3q": { + "v": "0.1.0", "with": "mailto:akiko@example.com", "do": "msg/send", "input": { - "body": { - "await/task/ok": { - "/": "bafyreih47gncsxq7ykxvfiecqdnwi566siv7rsv5iggyygq5eltvyxbz2y" + "to": { + "await/ok": { + "ucan/task": { + "/": "bafyreifc2ytuc5dv454a7c6cxk3mm7gt6skoptzj5rtc3ijf65v2bjxssy" + } } }, "subject": "Coffee", - "to": { - "await/task/ok": { - "/": "bafyreidgaki3ds2qqus4xz62lhjpycf5dy6p3kiyfurnesrlziucqglrya" + "body": { + "await/ok": { + "ucan/task": { + "/": "bafyreif7wiz4mz3ojmmvrz2p5m6ay5nqbi7ghwmheccv5rqgjtkrk5wyi4" + } } } }, @@ -1108,68 +1138,101 @@ const createDraft = crud.create("https://example.com/blog/posts", { title: "How UCAN Tasks Changed My Life", body: "This is the story of how one spec changed everything...", }, -}, { - proofs: [CID.parse('bafyreidxwlsckxfpyy4safgdyqq4lhlyxsreppfcfbh2373w53rslqb7ly')] }) -const getEditors = crud.read("https://example.com/users/editors", {}, { - proofs: [CID.parse('bafyreifytuitcmz47m63gibhfgzngzbjo5c6bfklxesgntplol3b7fek4i')] -}) +const getEditors = crud.read("https://example.com/users/editors") const notify = msg.send("mailto:akiko@example.com", { to: (await createDraft).ok, subject: "Coffee", body: (await getEditors).ok, -}, { - proofs: [CID.parse('bafyreibukcml2eggjterlo4zxxal4livmm2dou4jeizgwxnclqbvkbsrle')] }) ``` -While a Promise MAY be substituted for any field in a [Task], substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. +While a [Await] MAY be substituted for any field in a [Task], substituting the `do` field is NOT RECOMMENDED. The `do` field is critical in understanding what kind of action will be performed, and schedulers SHOULD use this fields to grant atomicity, parallelize tasks, and so on. -After resolution, the Invocation MUST be validated against the UCANs known to the Executor. A Promise resolved to an invocation that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. +After resolution, the [Invocation] MUST be validated against the [Authorization] and linked UCAN proofs by the [Executor]. A Promise resolved to an [Invocation] that is not backed by a valid UCAN MUST NOT be executed, and SHOULD return an unauthorized error to the user. A Promise resolved to an [Invocation] with the [Authorization] that does not include invoked [Task] MUST NOT be executed, and SHOULD return an unauthorized error to the user. -Promises MAY be used inside of a single Invocation, or across multiple Invocations, and MAY even be across multiple Invokers and Executors. As long as the invocation can be resolved, it MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). +Promises MAY be used across [Invocation]s with a same [Authorization], or across [Invocation]s with different [Authorization] and MAY even be across multiple Invokers and Executors. As long as the invocation can be resolved, it MAY be promised. This is sometimes referred to as ["promise pipelining"](http://erights.org/elib/distrib/pipeline.html). -A Promise SHOULD resolve to a [Result]. If a particular branch's value is required to be unwrapped, the Result branch (ok or error) MAY be specified. -An invocation MUST fail if promise is resolved does not match the specified branch. +## 8.1 Promise -## 7.1 Schema +A `Promise` describes eventual output `Result` of the [Invocation]. -The `Promise` describes a pointer to the eventual value in a Promise on either branch (`await/task/*`, `await/invocation/*`), or specifically the success (`await/task/ok`, `await/invocation/ok`) or failure (`await/task/error`, `await/invocation/error`) branches. +### 8.1.1 Schema ```ipldsch -type Promise union { - &Invocation "await/invocation/*" - &Invocation "await/invocation/ok" - &Invocation "await/invocation/error" - # If it is a task you MUST derive invocation by using authorization of - # this invocation - &Task "await/task/*" - &Task "await/task/ok" - &Task "await/task/error" +type Promise union { + | &Invocation ("ucan/invocation") + | &Task ("ucan/task") } representation keyed ``` -## 7.2 Variants +#### 8.1.2 Variants + +##### 8.1.2.1 Invocation Promise + +A `Promise` of the [Invocation] MUST be a link to that invocation wrapped in the `"ucan/invocation"` tag. + +```ts +{ + "ucan/invocation": { + "/": "bafyreihdw5dpnggixaee3rwn5kr3vhvfvokj3hi24uk2swp4nizyjdhdeq" + } +} +``` + +##### 8.1.2.2 Task Promise + +An `Promise` of the [Invocation] that shares [Authorization] with a [Task] referecing it MUST be a link to the invocation [Task] wrapped in the `"ucan/task"` tag. + +Note that [Task]s with the same [Authorization] will be unable to referece [Invocation] since [Authorization] CAN be created after all the [Task]s being authorized. The `"ucan/task"` tagged Promise MAY be used in such cases. + +## 8.2 Await + +An `Await` describes an output of the [Invocation]. An `Await` MUST resolve to an output [Result] with `await/*` variant. If unwrapping success or failure case is desired corresponding `await/ok` or `await/error` variants MUST be used. + +### 8.2.1 Schema + +```ipldsch +type Await union { + &Promise "await/*" + &Promise "await/ok" + &Promise "await/error" +} representation keyed +``` + +#### 8.2.2 Variants + +##### 8.2.2.1 Success + +The successful output of the [Invocation] MAY be referenced by wrapping a [Promise] in the `"await/ok"` tag. + +[Executor] MUST fail [Invocation] that `Await`s succeful output of the failed [Invocation]. + +[Executor] MUST substitute [Task] field set to the [Await] of the succesful [Invocation] with an (unwrapped) `ok` value of the output. + +##### 8.2.2.1 Failure -### 7.2.1 Relative Addressing +The failed output of the [Invocation] MAY be renfereced by wrapping a [Promise] in the `"await/error"` tag. -Promises keyed as `await/task/*`, `await/task/ok`, `await/task/error` use a `Task` link to reference an Invocation in the same [Authorization]. They MUST be resolved to `Invocation` with the same [Authorization] as the invocation using a promise. +[Executor] MUST fail [Invocation] that `Await`s failed output of the succesful [Invocation]. -> Note that [Task]s in the same [Authorization] will be unable to use `Invoaction` link because it would be impossible to sign an authorization and reference it from the [Task] linked from the authorization. +[Executor] MUST substitute [Task] field set to the [Await] of the failed [Invocation] with an (unwrapped) `error` value of the output. -### 7.2.2 +##### 8.2.2.1 Result -Promises keyed as `await/invocation/*`, `await/invocation/ok`, `await/invocation/error` use an [Invocation] when referencing an Invocation from different [Authorization]. +The [Result] output of the [Invocation] MAY be reference by wrapping a [Promise] in the `"await/*"` tag. +[Executor] MUST substitute [Task] field set to the [Await] of the [Invocation] with a `Result` value of the output. -## 5.2 Pipelines -Pipelining uses promises as inputs to determine the required dataflow graph. The following examples both express the following dataflow graph: +## 8.3 Dataflows -### 5.2.1 Batched +Pipelining uses [Await] as inputs to determine the required dataflow graph. The following examples both express the following dataflow graph: + +### 7.3.1 Batched ```mermaid flowchart BR @@ -1192,7 +1255,12 @@ flowchart BR ```json { "blocks": { - "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla": { + "bafyreiesse5saoa5dj7f5mh7sffy57vfhnjm6tpgwmwxe3ncwin2hwqsoy": [ + { "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" }, + { "/": "bafyreier7y2snffy7x75y4ri6ahiflxmz7ksklizzwpphwgpfotc3omo2y" } + ], + "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli": { + "v": "0.1.0", "with": "dns:example.com?TYPE=TXT", "do": "crud/update", "input": { @@ -1200,17 +1268,24 @@ flowchart BR }, "prf": [ { "/": "bafyreieynwqrabzdhgl652ftsk4mlphcj3bxchkj2aw5eb6dc2wxieilau" } - ] + ], }, - "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom": { + "bafyreieeipburgtzg4hr2ghxgspc53szrkstaz4syjat3tex75hjdrau3y": [ + { "/": "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm" }, + { "/": "bafyreier7y2snffy7x75y4ri6ahiflxmz7ksklizzwpphwgpfotc3omo2y" } + ], + "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm": { + "v": "0.1.0", "with": "mailto://alice@example.com", "do": "msg/send", "input": { - "to": "bob@example.com", "subject": "DNSLink for example.com", + "to": "bob@example.com", "body": { - "await/task/ok": { - "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" + "await/ok": { + "ucan/task": { + "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" + } } } }, @@ -1218,85 +1293,259 @@ flowchart BR { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } ] }, - "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq": { + "bafyreif7cskac7ongewanqahk2vljkk5rsiblm2vqqo57j2d6mgeuhtuxq": [ + { "/": "bafyreifjjvdd6hoi7wjot7tjaubwztqldds332bgq6a4n37d6s5slxkbvy" }, + { "/": "bafyreier7y2snffy7x75y4ri6ahiflxmz7ksklizzwpphwgpfotc3omo2y" } + ], + "bafyreifjjvdd6hoi7wjot7tjaubwztqldds332bgq6a4n37d6s5slxkbvy": { + "v": "0.1.0", "with": "mailto://alice@example.com", "do": "msg/send", "input": { "to": "carol@example.com", "subject": "Hey Carol, DNSLink was updated!", "body": { - "await/task/ok": { - "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" + "await/ok": { + "ucan/task": { + "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" + } } } }, "prf": [ { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } - ], + ] }, - "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um": { + "bafyreid5bb7z7l4ti57gdep6tbsnam47555ivnh3znpylo2n7qqyiiggqm": [ + { "/": "bafyreidxauqdrexpqw66zlo3q6cmr2vuuvb3vletnugkf33uuxwt2um4ry" }, + { "/": "bafyreier7y2snffy7x75y4ri6ahiflxmz7ksklizzwpphwgpfotc3omo2y" } + ], + "bafyreidxauqdrexpqw66zlo3q6cmr2vuuvb3vletnugkf33uuxwt2um4ry": { + "v": "0.1.0", "with": "https://example.com/report", "do": "crud/update", "input": { "payload": { + "event": "email-notification", "from": "mailto://alice@exmaple.com", - "to": ["bob@exmaple.com", "carol@example.com"], - "event": "email-notification" + "to": [ + "bob@exmaple.com", + "carol@example.com" + ] }, "_": [ { - "await/task/ok": { - "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" + "await/ok": { + "ucan/task": { + "/": "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm" + } } }, { - "await/task/ok": { - "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" + "await/ok": { + "ucan/task": { + "/": "bafyreifjjvdd6hoi7wjot7tjaubwztqldds332bgq6a4n37d6s5slxkbvy" + } } } - ] + ], }, "prf": [ { "/": "bafyreiflsrhtwctat4gulwg5g55evudlrnsqa2etnorzrn2tsl2kv2in5i" } - ], + ] }, - "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4": { - "v": "0.1.0", - "run": [ - { "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" }, - { "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" }, - { "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" }, - { "/": "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um" } + "bafyreier7y2snffy7x75y4ri6ahiflxmz7ksklizzwpphwgpfotc3omo2y": { + "scope": [ + { "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" }, + { "/": "bafyreifjjvdd6hoi7wjot7tjaubwztqldds332bgq6a4n37d6s5slxkbvy" }, + { "/": "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm" }, + { "/": "bafyreidxauqdrexpqw66zlo3q6cmr2vuuvb3vletnugkf33uuxwt2um4ry" } ], - "nnc": "abcdef", "s": { "/": { - "bytes": "7aEDQIcE6PG2nwLfi3zE/iT7scpYXKBCu5bfTVwIRFNCpAKpBnMp6eEHgsFg3mwDkX/BKpWdiWaQymNyTYQ+HXYNEgM" + "bytes": "7aEDQPdMfJiaNTzmTVHiJhkcX6mlKOG2piEk0OtpLslJeaimx4uM4/hGcadQ3Z6qhu2j761PW4RKyC1+BiWB+jO7LwA" } } + } + }, + "roots": [ + { "/": "bafyreiesse5saoa5dj7f5mh7sffy57vfhnjm6tpgwmwxe3ncwin2hwqsoy" }, + { "/": "bafyreif7cskac7ongewanqahk2vljkk5rsiblm2vqqo57j2d6mgeuhtuxq" }, + { "/": "bafyreieeipburgtzg4hr2ghxgspc53szrkstaz4syjat3tex75hjdrau3y" }, + { "/": "bafyreid5bb7z7l4ti57gdep6tbsnam47555ivnh3znpylo2n7qqyiiggqm" } + ] +} +``` + + +### 7.3.2 Serial + +```mermaid +flowchart TB + update-dns("with: dns:example.com?TYPE=TXT + do: crud/update") + notify-bob("with: mailto://alice@example.com + do: msg/send + to: bob@example.com") + notify-carol("with: mailto://alice@example.com + do: msg/send + to: carol@example.com") + + log-as-done("with: https://example.com/report + do: crud/update") + + subgraph start [ ] + update-dns + notify-bob + end + + subgraph finish [ ] + notify-carol + log-as-done + end + + update-dns -.-> notify-bob + update-dns --> notify-carol + notify-bob --> log-as-done + notify-carol -.-> log-as-done +``` + +```json +{ + "blocks": { + "bafyreid5ltmhwuhgcbxa3dwd5erymqdpfsyhlwudlfylu3wrzy6fjbvuoy": [ + { "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" }, + { "/": "bafyreicwrzsyonu4efnwtmwqax2s2fbv5padbyk66zwl6kkvrkrd5qavpy" } + ], + "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli": { + "v": "0.1.0", + "with": "dns:example.com?TYPE=TXT", + "do": "crud/update", + "input": { + "value": "hello world" + }, + "prf": [ + { "/": "bafyreieynwqrabzdhgl652ftsk4mlphcj3bxchkj2aw5eb6dc2wxieilau" } + ] }, - "bafyreih5lzopso4jtwq7v2mt33e5p2ihuvqxmsmpiz34z7ptc5myraxara": [ - { "/": "bafyreiej6eephmdkbsepd2vt37b7llyjaddnflb6v7grdq6p4pxgbwxsla" }, - { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + "bafyreigbtlydjneybqpy6r3u5hobsempr335tjw4ozm2aun5yrcj2whdgi": [ + { "/": "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm" }, + { "/": "bafyreicwrzsyonu4efnwtmwqax2s2fbv5padbyk66zwl6kkvrkrd5qavpy" } ], - "bafyreiawyrlpqzex2xabvghi7eiuuy47ida5tfkwjugsgh4mc4tuaeteja": [ - { "/": "bafyreifkepdop7vrw4i6reeyzgxbj2alidkhvyccm7p6bhfczh2k4bagom" }, - { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm": { + "v": "0.1.0", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "body": { + "await/ok": { + "ucan/task": { + "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" + } + } + }, + "subject": "DNSLink for example.com", + "to": "bob@example.com" + }, + "prf": [ + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } + ] + }, + "bafyreicwrzsyonu4efnwtmwqax2s2fbv5padbyk66zwl6kkvrkrd5qavpy": { + "scope": [ + { "/": "bafyreigb54cv7jl4rt32nl5r7udzhbavd7c4ct5pkfkuept2ufrpig3zli" }, + { "/": "bafyreieahqhc2dwrnucyljhpqqsskqak2tc4ehhd6lvxpzuztx72k4xidm" } + ], + "s": { + "/": { + "bytes": "7aEDQKrLlk87jEJh2ftRMXidqypRf2QtwFUvcTWvKN9G1D5XeLB8o6TtPv2qTF/b+s9r6DAQAavilk8J10yFYDyMUAA" + } + } + } + }, + "roots": [ + { "/": "bafyreid5ltmhwuhgcbxa3dwd5erymqdpfsyhlwudlfylu3wrzy6fjbvuoy" }, + { "/": "bafyreigbtlydjneybqpy6r3u5hobsempr335tjw4ozm2aun5yrcj2whdgi" } + ] +} +``` + +```json +{ + "blocks": { + "bafyreif4zur3xni5fal23ybxjsw4bm5ezfjp6xgfwjd5ndextr55kr4i34": [ + { "/": "bafyreigl5x2p3ehppg7xbwexdsr63ljfkcr4oj76lwacwj4rt5vfs2cvky" }, + { "/": "bafyreiacpop3w22qc722sdovynbwi2so6r2xetqx5hf4xz326iu3we5sqa" } ], - "bafyreidwsezjbi5taeedhyn6n3i7hlmbkglvgtpim67e4wrm6iolprurse": [ - { "/": "bafyreidfkvsjw2vya4leyudwjwoptwkgk6de5oxf4cydjpxwpdfxrqvhlq" }, - { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } + "bafyreigl5x2p3ehppg7xbwexdsr63ljfkcr4oj76lwacwj4rt5vfs2cvky": { + "v": "0.1.0", + "with": "mailto://alice@example.com", + "do": "msg/send", + "input": { + "to": "carol@example.com", + "subject": "Hey Carol, DNSLink was updated!", + "body": { + "await/ok": { + "ucan/invocation": { + "/": "bafyreid5ltmhwuhgcbxa3dwd5erymqdpfsyhlwudlfylu3wrzy6fjbvuoy" + } + } + } + }, + "prf": [ + { "/": "bafyreibblnq5bawcchzh73nxkdmkx47hu64uwistvg4kyvdgfd6igkcnha" } + ] + }, + "bafyreibvwhdqydz4qq3narocqsvnqbxoegxgmmlp35mewn53rjxeltfccu": [ + { "/": "bafyreifaip4infpqh76r7nlscubkfznhoiko224i2sthy7w3atfsmrff2a" }, + { "/": "bafyreiacpop3w22qc722sdovynbwi2so6r2xetqx5hf4xz326iu3we5sqa" } ], - "bafyreiby75y3vpappphkarojt5gr4oxwviqn3oor57q6q3l2yqmqjl4mcu": [ - { "/": "bafyreih7z6nrzp6mx2cphvzwnt6ief6x5skdjkxoafqemseltgxcmly5um" }, - { "/": "bafyreibk6k2tjretvbebvxrkuqwak56yok635awraxd6uqwu3ut7j3b2u4" } - ] + "bafyreifaip4infpqh76r7nlscubkfznhoiko224i2sthy7w3atfsmrff2a": { + "v": "0.1.0", + "with": "https://example.com/report", + "do": "crud/update", + "input": { + "payload": { + "from": "mailto://alice@exmaple.com", + "to": ["bob@exmaple.com", "carol@example.com"], + "event": "email-notification" + }, + "_": [ + { + "await/ok": { + "ucan/invocation": { + "/": "bafyreigbtlydjneybqpy6r3u5hobsempr335tjw4ozm2aun5yrcj2whdgi" + } + } + }, + { + "await/ok": { + "ucan/task": { + "/": "bafyreifjjvdd6hoi7wjot7tjaubwztqldds332bgq6a4n37d6s5slxkbvy" + } + } + } + ] + }, + "prf": [ + { "/": "bafyreiflsrhtwctat4gulwg5g55evudlrnsqa2etnorzrn2tsl2kv2in5i" } + ] + }, + "bafyreiacpop3w22qc722sdovynbwi2so6r2xetqx5hf4xz326iu3we5sqa": { + "scope": [ + { "/": "bafyreigl5x2p3ehppg7xbwexdsr63ljfkcr4oj76lwacwj4rt5vfs2cvky" }, + { "/": "bafyreifaip4infpqh76r7nlscubkfznhoiko224i2sthy7w3atfsmrff2a" } + ], + "s": { + "/": { + "bytes": "7aEDQBCcyIzfbMRT/UnUF3LgY7Xp/hhhpJfr4kRuEQEgmxcATSE1iLD7VOLMA0nauhqRM7RFUIGjxt1CAf4tZY+yOQE" + } + } + } }, "roots": [ - { "/": "bafyreih5lzopso4jtwq7v2mt33e5p2ihuvqxmsmpiz34z7ptc5myraxara" }, - { "/": "bafyreiawyrlpqzex2xabvghi7eiuuy47ida5tfkwjugsgh4mc4tuaeteja" }, - { "/": "bafyreidwsezjbi5taeedhyn6n3i7hlmbkglvgtpim67e4wrm6iolprurse" }, - { "/": "bafyreiby75y3vpappphkarojt5gr4oxwviqn3oor57q6q3l2yqmqjl4mcu" } + { "/": "bafyreif4zur3xni5fal23ybxjsw4bm5ezfjp6xgfwjd5ndextr55kr4i34" }, + { "/": "bafyreibvwhdqydz4qq3narocqsvnqbxoegxgmmlp35mewn53rjxeltfccu" } ] } ``` @@ -1333,24 +1582,25 @@ Thanks to [Christine Lemmer-Webber](https://github.com/cwebber) for the many con Thanks to [Rod Vagg](https://github.com/rvagg/) for the clarifications on IPLD Schema implicits and the general IPLD worldview. -[ucan invocation]: https://github.com/ucan-wg/invocation/tree/rough + [dag-json]: https://ipld.io/docs/codecs/known/dag-json/ [varsig]: https://github.com/ChainAgnostic/varsig/ [ipld schema]: https://ipld.io/docs/schemas/ -[varsig]: https://github.com/ChainAgnostic/varsig [ucan-ipld]: https://github.com/ucan-wg/ucan-ipld/ [ucan]: https://github.com/ucan-wg/spec/ [dag-cbor]: https://ipld.io/specs/codecs/dag-cbor/spec/ [car]: https://ipld.io/specs/transport/car/carv1/ -[result]: #4221-Result -[extended result]: #4222-Extended-Result -[pipelines]: #52-Pipelines -[task]: #3-Task -[authorization]: #4-Authorization -[invocation]: #4-Invocation -[receipt]: #5-receipt -[promise]: #6-promise -[executor]: #323-executor -[invoker]: #322-invoker [ipld representation]:https://ipld.io/docs/schemas/features/representation-strategies/ [lazy-vs-eager]: #112-Lazy-vs-Eager-Evaluation +[invoker]: #211-invoker +[executor]: #212-executor +[task]: #3-task +[authorization]: #4-authorization +[invocation]: #5-Invocation +[result]: #6-Result +[receipt]: #7-receipt +[pipelines]: #8-Pipelines +[promise]: #81-promise +[await]: #82-await +[dataflows]: #83-await +