Multiple rulesets to layer functionality
In some sense, a pico is like a personal computer in the cloud. It reacts to events and responds to queries in a way defined by its installed rulesets.
For ruleset identifiers, "We recommend using the Java convention of using the reversed name of a domain that you control, followed by a name that is unique within your organization. For example, we use the RID io.picolabs.wrangler
for one of our internal rulesets." (see Grammar page)
Every pico created has some system rulesets installed. These are like the OS of a personal computer. An application installs further rulesets, expanding the range of events and queries to which the pico will react and respond. Additional rulesets installed in a pico can layer on additional functionality.
We will outline here an example, layering general agent functionality on a pico, and then specializing further for connections to a specific kind of agent.
Agent Picos
The Sovrin self-sovrin identity solution calls for users to be represented by agents. To make a pico into a Sovrin agent, all that is required is to install the org.sovrin.agent
ruleset. A simple UI is built in as the pico's Agent tab.
org.sovrin.agent
ruleset
This ruleset, and the behaviors it implements, are specified by the Sovrin foundation. Therefore, the ruleset identifier uses the reversed domain name controlled by that organization, and it was written by a person on behalf of that organization. We show a few excerpts from this ruleset here:
ruleset org.sovrin.agent { meta { use module io.picolabs.wrangler alias wrangler ... } global { ... } // // on ruleset_added // rule on_installation { select when wrangler ruleset_added where event:attr("rids") >< meta:rid ... } ... // // accept invitation // rule accept_invitation { select when sovrin new_invitation url re#^(http.+[?].*c_i=.+)# setting(url) ... } ... }
We can see (in line 3) that this ruleset uses io.picolabs.wrangler
, one of the system-supplied rulesets, as a module
.
In addition, when this (org.sovrin.agent
) ruleset is installed, it reacts to a wrangler:ruleset_added
event raised by Wrangler, by executing the on_installation
rule (lines 12-15).
A agent-to-agent connection can begin when this pico accepts an invitation from another agent, using the accept_invitation
rule (lines 20-23). The Agent tab of the UI has a box into which the invitation URL can be pasted, and a button that can be clicked to send this event to the pico.
Notice that the event must include an attribute named url
that corresponds to the pattern shown in the regular expression of line 21. Informally, the URL must begin with "http" and sometime later contain a question mark, followed directly (or later) by the string "c_i=" and some additional text. The "c_i" is the compressed (and Base 64 encoded) invitation message.
If the pico receives the sovrin:new_invitation
event, but there is no url
attribute, or that attribute does not satisfy this pattern, then this rule is not selected and will not be executed.
Complete source code for this ruleset can be found on GitHub.
Connecting to a streetcred agent
The streetcred organization provides agents using an app for smart phones.
It is common to present agent invitations as a QR Code. A full invitation URL is quite lengthy and produces a very large scannable image. To mitigate this, streetcred uses a much shorter invitation URL, resulting in a more manageable QR Code. This short URL does not include a "c_i" parameter. So pasting it into the Agent tab of the UI will not initiate a connection.
The streetcred short invitation, when visited, produces an HTTP redirect to the longer form of the invitation. The HTTP response to the short URL looks like this (with some lines omitted and long lines truncated):
{ "content": "", "content_type": null, "content_length": 0, "headers": { "date": "Wed, 21 Aug 2019 20:17:15 GMT", "content-length": "0", ..., "location": "id.streetcred://launch/?c_i=eyJsYWJlbCI6IkJBQyBpUGhvbmUiLCJpbWFnZVVybCI6bnVsbCwic2VydmljZ...vbiJ9", ... }, "status_code": 302, "status_line": "Found" }
The status_code
value, 302
means that the actual resource we are looking for (the agent-to-agent invitation) is in a different location
, specified by the "location" header. Notice that this URL doesn't use the http (or https) scheme, but rather a custom scheme, "id.streetcred" as an indication to the smart phone to launch the streetcred app. Also notice that the URL does contain a parameter named "c_i" which is the actual invitation message (Base 64 encoded).
A rule like the one shown below would accept the short streetcred URL and re-raise the sovrin:new_invitation
event with a url
attribute which would cause the existing accept_invitation
rule in the org.sovrin.agent
ruleset to be selected, and executed, making the connection.
rule accept_streetcred_invitation { select when sovrin new_invitation url re#^(https://redir.streetcred.id/.+)# setting(url) pre { res = http:get(url,dontFollowRedirect=true) ok = res{"status_code"} == 302 location = ok => res{["headers","location"]} | null } if location && location.match(prefix) then noop() fired { raise sovrin event "new_invitation" attributes event:attrs.put("url","http://"+location) } }
Notice that this rule selects on the same event, but has a different requirement for the URL. In fact, its pattern (line 3) exactly matches the format of the streetcred short invitation URL.
Line 5 uses the http:get()
function (of the http
module) to obtain the actual invitation URL from streetcred. By default, the module function would follow the redirection, but we don't want that here, so we use the optional dontFollowRedirect
parameter. As we saw above, the status_code
will be 302
so we will get the long form of the invitation URL from the location
header (line 7).
Assuming the longer URL matches (using the String operator match()
) the expected pattern (described again below), this rule will raise the sovrin:new_invitation
event (again!), re-writing the original URL with the longer one left-padded to match the pattern of the accept_invitation
rule. This rule will be selected this time, and the connection will be initiated.
Now we could add this rule to the org.sovrin.agent
ruleset. That ruleset is part of a public repository, so we could make a pull request.
id.streetcred.redir
ruleset
Or, we could create an additional ruleset, intended to be layered on top of the org.sovrin.agent
ruleset.
ruleset id.streetcred.redir { global { prefix = re#^id.streetcred://launch/[?]c_i=# } // // convert streetcred invitation into one acceptable to Agent Pico // rule accept_streetcred_invitation { select when sovrin new_invitation url re#^(https://redir.streetcred.id/.+)# setting(url) ... } }
Here we can see both the expected pattern of the short URL (line 10) and the longer one (line 3, in the global name prefix
used earlier).
Chain of events
Supposing that we have an Agent Pico (which by definition has the org.sovrin.agent
ruleset) which also has the id.streetcred.redir
ruleset installed.
When we paste the short streetcred URL into the Agent tab (in the box inviting "paste invitation URL here") and click on the "Accept" button, the UI sends the sovrin:new_invitation
event to the pico.
As the pico engine looks through the pico's rulesets, it will not select the accept_invitation
rule from the org.sovrin.agent
ruleset. But, it will select the accept_streetcred_invitation
rule of the id.streetcred
ruleset. This is because the URL does not match the regular expression of the former, but does match the regular expression of the latter.
As the accept_streetcred_invitation
rules executes, it will raise the sovrin:new_invitation
event (again) this time passing it a url
attribute which matches the rule in org.sovrin.agent
but does not match the rule in id.streetcred.redir
. So this time, the accept_invitation
rule will select and as it executes it will begin the agent-to-agent connection using the c_i
parameter provided in the longer URL.
Copyright Picolabs | Licensed under Creative Commons.