Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

v0.1.0 #1

Closed
wants to merge 127 commits into from
Closed

v0.1.0 #1

wants to merge 127 commits into from

Conversation

expede
Copy link
Member

@expede expede commented Nov 26, 2022

📜 Preview

Pulling the low-level capability invocation bits out of ipvm-wg/spec#8 to UCAN because this layer doesn't have any direct IPVM dependencies

@expede expede marked this pull request as draft November 26, 2022 01:33
@expede expede self-assigned this Nov 26, 2022
johnandersen777 pushed a commit to johnandersen777/use-cases that referenced this pull request Nov 26, 2022
@expede
Copy link
Member Author

expede commented Nov 26, 2022

Hmm invocation and pipelining don't have to be in the same spec. It could be broken down really granularly:

UCAN -> Invocation -> Receipt -> Pipeline
                    ⌞_____________________⌟
                       These two probably
                         make most sense
                            together

@expede
Copy link
Member Author

expede commented Nov 26, 2022

@pdxjohnny GitHub snitched on you above 😉

Here another work-in-progress that may be relevant:

You've likely seen it already, but just in case not :)

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated

Note that this does not guarantee correctness of the result! The level of this statement's veracity MUST be ony taken that the signer claims this to be a fact.

The receipt MUST be signed with by the `aud` from the UCAN.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this to requirement to be difficult to meet. In our setup actor doing the work would not have access to the key to sign the result, but it would have some delegation from the aud to act on it's behalf.

I think it would be a good idea to consider such a setup and how delegates could sign on behalf of aud.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that if a subdelegate is going to do the work, you need to create an invocation for them

alice --[delegate]--> bob --[delegate]--> carol --[delegate]--> dan
                                &                     &
                              Invoke                Invoke

Here we have both an delegation chain (the right to do the thing), and a wrapper invocation. Bob asks Carol to do the work. She doens't have access to everything required for the job, so she subdelegates a partial job to Dan and wraps it in another invocation.

Does that not match your picture?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is that invocation will target web3.storage which has some did:key. But service that receives that invocation will not have access to that key, so it will not be able to sign receipt with it.

Service could have some delegation from web3.storage key as an authorization to act on it’s behalf, which it will get earlier than invocation.

This is more or less what I’ve been trying to accomplish with pipelining/forwarding threads. I

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooooh interesting @Gozala! You're trying to do that statelessly. The other way of modeling that is of course with a (stateful) DID document.

The problem with not aligning the audience in the invocation is that if you need to coordinate with an external service, you won't be able to prove the chain. For example, let's say that Fission starts using web3.storage for storage, and web3.storage uses Fission for DNSLink management. We would have no way to prove that the user intends to update their DNSLink if they started a request from your service (to abstract away the fact that there's many services here).

I agree that the forwarding concept would alleviate this! Also agree that "sure we'll run anything aimed at that DID" also works for a subset of cases where you're handing the entire request. If you want a service to openly interoperate, "the chain must be unbroken".

So: if we ship the forwarding/powerbox feature, does that make invocation better for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooooh interesting @Gozala! You're trying to do that statelessly. The other way of modeling that is of course with a (stateful) DID document.

You could do with did docs but that indeed introduces state and has additional tradeoffs that I’d like to avoid.

I’m not trying to make it stateless, but rather trying to capture state in the receipt so it could be verified even after state change

The problem with not aligning the audience in the invocation is that if you need to coordinate with an external service, you won't be able to prove the chain. For example, let's say that Fission starts using web3.storage for storage, and web3.storage uses Fission for DNSLink management. We would have no way to prove that the user intends to update their DNSLink if they started a request from your service (to abstract away the fact that there's many services here).

I think we misunderstand each other. All I’m proposing is just like agent could delegate invocation to another agent, it should be possible to delegate execution to another agent.

In other words I could delegate to you file upload capability. But so should upload service be able to delegate providing upload capability to another service. That way agent invoking capability does not need to know which upload service web3.storage relies on.

It is possible to accomplish above with active forwarding of delegations where our service accepts invocation and then forwards (redelegates) it to desired handler. But that implies signing things at every request and access to the keys.

Ideally it would be possible to just route request to a desired handler, who could sign receipt with own key and provide a proof chain warranting it to handle such requests on behalf of the aud service.

So: if we ship the forwarding/powerbox feature, does that make invocation better for you?

That might be a way to address it, but there also might be different ways to address this specific problem without changing UCANs themselves

Copy link
Member Author

@expede expede Nov 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not trying to make it stateless, but rather trying to capture state in the receipt so it could be verified even after state change

Sorry, this is what I mean by "stateless" — in the same sense REST. It doesn't make reference to ambient external state, but everything required to fulfill the request is passed along together.

That way agent invoking capability does not need to know which upload service web3.storage relies on.

Absolutely! My understanding about this scenario is that web3.storage would be unable to further delegate to external services using UCAN 0.9. Is that correct?

Want:
Alice -> Bob -> web3.storage[bot1, bot2, bot3] -> fission.codes

But have these disconnected:

Alice -> Bob -> web3.storage
bot2 -> fission.codes

This scenario would be impossible, because web3.storage would not be able to subdelegate to fission.codes under UCAN 0.9

This can be fixed with your forwarding/powerbox idea to connect the two disconnected chains in the proposed UCAN 0.10 changes

Alice -> Bob -> web3.storage

web3.storage =[fwd]=> bot1
web3.storage =[fwd]=> bot2
web3.storage =[fwd]=> bot3

bot2 -> fission.codes

Which composes as something like:

Alice -> Bob -> web3.storage
                web3.storage =[fwd]=> bot2
                                      bot2 -> fission.codes

This is my current picture/plan for 0.10 ☝️ Does that match yours?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we may actually need an invocation chain in the Receipts 🤔

README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
@johnandersen777
Copy link

@pdxjohnny GitHub snitched on you above 😉

Here another work-in-progress that may be relevant:

You've likely seen it already, but just in case not :)

/me screams with excitement 😝👍👍 oh boy oh boy do I have some reading to do. I love what I’m seeing I hadn’t seen this and I will be digging!

README.md Outdated Show resolved Hide resolved
@expede
Copy link
Member Author

expede commented Nov 26, 2022

😆 I'm having to break certain features across IPVM and UCAN. Organization is hard!

@expede expede mentioned this pull request Nov 26, 2022
@expede
Copy link
Member Author

expede commented Dec 16, 2022

@Gozala

proposed v0.10 syntax is possibly interpreted as a logical AND not an OR as presented above. (While we haven't worked out the exact details in v0.10 yet, I would expect this to be interpreted as an AND).

I'm not sure I follow what you mean by AND vs OR here to be honest.

If you have this:

{
  "https://example.com": {
    "crud/update": [
      {"day-of-week": "Friday", "time-of-day": "afternoon"},
      {"content-type": "application/json"}
    ]
  }
}

I expect this UCAN to restrict actions to updates only on Friday afternoons AND with application/json, not appliction/json OR anything on Fridays. The elements in the now-unified nb field act as an AND, or as an OR.

My interpretation was always AND with some transactional guarantees implied as in do store/add then store/list ... and fail the whole thing if any of the steps fail. Which is why we have decided to limit to single capability, that way transactional guarantees became trivial.

Ah, I wasn't talking about the transactionality there.

With new format there is no implied order so I think it's reasonable to treat them as concurrent tasks unless they pipe into each other and even when they pipe it still reasonable to expect that task been piped to can fail.

Yes, only ordered via dependencies expressed in promises

As you're saying that you're forcing a single capability at the top level definitely disambiguates, too (though there are other tradeoffs like the number of signatures involved).

With a new format I have had been reviving queries with selectors approach, with above rational single capability per invocation is less of a concern. But it does bring single vs multiple aud concern into a focus. I'm not entirely sure where I'll land on this.

Thanks for being open to exploring the design space!

@Gozala
Copy link
Contributor

Gozala commented Dec 16, 2022

I expect this UCAN to restrict actions to updates only on Friday afternoons AND with application/json, not appliction/json OR anything on Fridays. The elements in the now-unified nb field act as an AND, or as an OR.

I see what you mean here. That as not my interpretation though, I was thinking them as AND is sense of you can do this and that (set union) as opposed you can do it if you meet both this and that constraints (set intersection).

But clearly UCAN spec should specify this if it goes this route.

Copy link
Contributor

@Gozala Gozala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look great @expede thanks for leading this effort. There are certain things that I'd like to address before committing to implementing this, however I think it would be easier to iterate over them in separate issue / pull request threads as fallow ups.

How about we call this a draft to signal that some followup work is intended ?

README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated
"event": "email-notification",
},
"_": [
{"ucan/ok": ["/", "notify-bob"]},
Copy link
Contributor

@Gozala Gozala Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not big fan of this approach as it is not obvious if task handler is supposed to not get anything in the _ input or if it should actually be provided the result of the ok branch ? I think I would much rather have something like ucan/await or ucan/_ that resolves to {} or Null that way you can stick those fields anywhere

Copy link
Member Author

@expede expede Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that in a previous version, but the feedback for a couple reviewers was that it would be better in the inputs because it's simpler to reason about one mechanism rather than several. I honestly don't have strong feelings about this.

Copy link
Member Author

@expede expede Dec 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, reading that again, we didn't have this part before:

resolves to {} or Null that way you can stick those fields anywhere

We can namespace this as ucan/_, though it's common practice to call thrown-away fields _. The Task that returns a value can be anything, even if we don't need the value. This is here to add temporal constraints using the same mechanism that we already have (promises).

We can't have several fields with the same value, so I guess what you're suggesting is to make them always their own object? e.g. {"promise/_": ["/", "task-name"]}. The runtime needs to remove them from the inputs before they're run, because passing {} or Null to a function may have an actual semantic meaning.

Alternately we can put a field like await: [Qm123, Qm456, Qm789] at the level above (next to with and do), but now we have two mechanisms for describing temporal ordering.

Copy link
Member Author

@expede expede Dec 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah, the easiest thing may be to go back to adding an after: [Promise] field — it may be conceptually cleaner than altering/ignoring parts of the inputs

{
  "with": "https://example.com/posts",
  "do": "crud/create",
  "inputs": {/* ... */},
  "after": [{"promise/ok": ["/", "some-earlier-task"]}]
}

AKA

struct Task {
  with URI
  do String
  inputs Any
  after optional [Promise]
}

Copy link
Member Author

@expede expede Dec 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other criticism has been that the Task meta field gets abused for too many things. Here's an alternate that I assume may fit many common needs:

type Task struct {
  with           URI
  do             String
  inputs         Any
  --
  nonce          String
  after optional [Promise]      -- Promise enables e.g. failing on `err`
  max   optional {String : Any} -- Disk, memory, time, gas, etc
  meta  optional {String : Any} -- Extensibility, but mainly for humans
}

expede and others added 5 commits December 16, 2022 21:56

### 9.1.1 Task

The `inv` field MUST include a link to the Task that the Receipt is for.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be ran as above?

Suggested change
The `inv` field MUST include a link to the Task that the Receipt is for.
The `ran` field MUST include a link to the Task that the Receipt is for.


### 9.1.1 Task

The `inv` field MUST include a link to the Task that the Receipt is for.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `inv` field MUST include a link to the Task that the Receipt is for.
The `inv` field MUST include a link to the InvokedTaskPointer that the Receipt is for.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see this comment. Different sections ipld scheme describe this differently, and I'm not sure which is latest


### 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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `ran`, `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.


### 6.2.6 Signature

The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `prf`, and `nnc` fields.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `inv`, `prf`, and `nnc` fields.
The `sig` field MUST contain a [Varsig](https://github.com/ChainAgnostic/varsig) of the `run`, `prf`, and `nnc` fields.

"with": "mailto://alice@example.com",
"do": "msg/send",
"inputs": {
"to": {"promise/*": ["/", "get-mailing-list"]}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"to": {"promise/*": ["/", "get-mailing-list"]}
"to": {"promise/*": ["/", "mailingList"]}

I'm not sure if this is the best replacement. Where did get-mailing-list get defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, indeed, this is actually probably the other way around to keep it consistent. A lot of these are me updating various versions of the spec as it was evolving quickly; thanks for the suggestion @gobengo !

"dev/tags": ["friends", "coffee"],
"dev/priority": "high",
"dev/notes": {
"select-task": ["/", "0"] // <- Pointer here
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"select-task": ["/", "0"] // <- Pointer here
"select-task": ["/", "1"] // <- Pointer here


``` ipldsch
type Receipt struct {
ran &InvokedTaskPointer
Copy link

@gobengo gobengo Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ran &InvokedTaskPointer

In section 2.3 it says ran &InvocationPointer. Which is correct?

Either way, if an InvocationPointer is involved (directly or indirectly via InvokedTaskPointer.envl), what if the InvocationPointer value is "/" in a receipt? If that is a 'relative' InvocationPointer, it's not obvious from the receipt alone what invocation it is relative to.

wdyt of

ran &Invocation

If you do mean ran &InvokedTaskPointer, which I think is meant to select a specific labeled task from the Invocation, it seems like out and rec would no longer need to be string-keyed maps, because ran would always identify a specific task sans-label.


### 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`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see this comment. if ran &InvokedTaskPointer, which selects a single task do these fields need to be 'indexed by the task name inside the invocation'?

type Closure struct {
with URI
do Ability
inputs Any
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does detection of promises in here work? based on below, it seems like the following will have promises resolved (dag-json ish).

"inputs": {
       "_": [
         {"promise/ok": ["/", "notify-bob"]},
         {"promise/ok": ["/", "notify-carol"]}
       ]
}

and

"inputs": {
       "notifyBobResponse": {"promise/ok": ["/", "notify-bob"]}
}

but will this?

"inputs": {
       "notifyBobResult": {
         "ok": {"promise/ok": ["/", "notify-bob"]}
       }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup that last one MUST also detect the promise. The rules are the same as for {"/": ...} in DAG-JSON. Thanks for asking; I'll add clarifying text to the spec 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.