Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

provider_allowed_or_short

Disjunction and parentheses over two information providers: permit Read when the document is EITHER explicitly allowlisted (Lists::Allowed) OR shorter than 4 characters (Strings::Length) — (A || B) — showing the || operator and parentheses in a when body across two different providers.

The trace exercises both branches and both verdicts:

  • @0readme is on the allowlist → ALLOW (left branch).
  • @10hi is 2 chars, so length < 4ALLOW (right branch).
  • @20longfilename is neither allowlisted nor short → DENY.
  • @30manifest is on the allowlist → ALLOW (left branch).

The two Rhai providers are declared in providers.json. Because the dogwood CLI parses declarations with ProviderDeclarations::from_json (which does not resolve scriptFile), the script bodies are inlined into providers.json via the script field. allowed.rhai and length.rhai are kept alongside as the readable source of those inlined bodies.

Referenced by guide/05-information-providers.md.

Policy

// Permit Read when the document is EITHER explicitly allowlisted OR short
// enough (A || B) -- showing the || operator and parentheses in a when body,
// over two different providers.
@id("read_allowed_or_short")
permit (
    principal,
    action == Drupe::Action::"Read",
    resource
)
when {
    (Lists::Allowed(context.input.document).allowed == true
     || Strings::Length(context.input.document).length < 4)
};

Schema

namespace Drupe {
  type ReadInput = {
    document: String
  };

  entity Gateway;

  entity OAuthUser = {
    id: String
  } tags String;

  action "Read" appliesTo {
    principal: [OAuthUser],
    resource: [Gateway],
    context: {
      input: ReadInput
    }
  };
}

Trace

@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "readme" }) Drupe::Action::"Read"::request(input: { document: "readme" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "hi" }) Drupe::Action::"Read"::request(input: { document: "hi" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "longfilename" }) Drupe::Action::"Read"::request(input: { document: "longfilename" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "manifest" }) Drupe::Action::"Read"::request(input: { document: "manifest" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u4")

Expected Output

@0 (time point 0): ALLOW  [rules: 0]
@10 (time point 1): ALLOW  [rules: 0]
@20 (time point 2): DENY
@30 (time point 3): ALLOW  [rules: 0]

Provider Declarations

{
  "availableProviders": {
    "Lists::Allowed": {
      "argumentTypes": [
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "allowed": {
            "paramType": "bool"
          }
        },
        "required": [
          "allowed"
        ]
      },
      "implementation": {
        "kind": "rhai",
        "script": "fn evaluate(text) {\n    // Defensive per the provider contract: a provider may be evaluated\n    // for ANY decision event, so any argument may be absent (unit).\n    // Return a conforming sentinel instead of erroring (errors are UB).\n    if type_of(text) == \"()\" {\n        return #{ allowed: false };\n    }\n\n    let allow = text == \"readme\" || text == \"manifest\";\n    #{ allowed: allow }\n}\n"
      }
    },
    "Strings::Length": {
      "argumentTypes": [
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "length": {
            "paramType": "integer"
          }
        },
        "required": [
          "length"
        ]
      },
      "implementation": {
        "kind": "rhai",
        "script": "fn evaluate(text) {\n    // Defensive per the provider contract: a provider may be evaluated\n    // for ANY decision event, so any argument may be absent (unit).\n    // Return a conforming sentinel instead of erroring (errors are UB).\n    if type_of(text) == \"()\" {\n        return #{ length: -1 };\n    }\n\n    #{ length: text.len() }\n}\n"
      }
    }
  }
}