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.