-
Notifications
You must be signed in to change notification settings - Fork 5
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
Conversation
Hmm invocation and pipelining don't have to be in the same spec. It could be broken down really granularly:
|
@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
|
||
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. |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🤔
/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! |
😆 I'm having to break certain features across IPVM and UCAN. Organization is hard! |
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
Ah, I wasn't talking about the transactionality there.
Yes, only ordered via dependencies expressed in promises
Thanks for being open to exploring the design space! |
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. |
There was a problem hiding this 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
Outdated
"event": "email-notification", | ||
}, | ||
"_": [ | ||
{"ucan/ok": ["/", "notify-bob"]}, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]
}
There was a problem hiding this comment.
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
}
Co-authored-by: Irakli Gozalishvili <contact@gozala.io> Signed-off-by: Brooklyn Zelenka <be.zelenka@gmail.com>
|
||
### 9.1.1 Task | ||
|
||
The `inv` field MUST include a link to the Task that the Receipt is for. |
There was a problem hiding this comment.
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?
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"select-task": ["/", "0"] // <- Pointer here | |
"select-task": ["/", "1"] // <- Pointer here |
|
||
``` ipldsch | ||
type Receipt struct { | ||
ran &InvokedTaskPointer |
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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"]}
}
}
There was a problem hiding this comment.
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 👍
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