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_int_arithmetic_trusted

A provider’s integer output used inside arithmetic, mixed under && with a plain Cedar condition on context.input — something only the unwrapped form allows (the closed guardrails { … } grammar cannot express it).

Strings::DigitCount(context.input.document).count counts [0-9] characters in the document, and the guard is “trusted and at most two digits” (count + 1 <= 3count <= 2):

trusteddocumentdigitsverdict
trueab0ALLOW
truea1b22ALLOW
truea1b2c33DENY
falseab0DENY

The Strings::DigitCount provider is declared in providers.json. Because the dogwood CLI parses providers.json without resolving external scriptFile references, the Rhai implementation (originally digits.rhai) is inlined into the script field of the declaration. digits.rhai is kept alongside for readability.

Schema, provider declaration, and Rhai script are lifted from corpus case 0010_unwrapped_mixed_with_cedar; the schema’s ReadInput carries an extra trusted: Bool field so the plain Cedar condition has something to read.

Reproduce:

dogwood validate policy.dw --policy-schema schema.cedarschema --providers providers.json
dogwood replay  policy.dw --policy-schema schema.cedarschema --providers providers.json --trace trace.log

Run with the bundle directory as the working directory.

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

Policy

// Permit Read when the request is flagged trusted AND the document has at most
// two digits (expressed as digitCount + 1 <= 3). Shows a provider's integer
// output used inside ARITHMETIC, mixed under && with a plain Cedar condition on
// context.input -- something only the unwrapped form allows.
@id("read_trusted_and_few_digits")
permit (
    principal,
    action == Drupe::Action::"Read",
    resource
)
when {
    context.input.trusted == true
    && Strings::DigitCount(context.input.document).count + 1 <= 3
};

Schema

namespace Drupe {
  type ReadInput = {
    document: String,
    trusted: Bool
  };

  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: "ab", trusted: true }) Drupe::Action::"Read"::request(input: { document: "ab", trusted: true }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "a1b2", trusted: true }) Drupe::Action::"Read"::request(input: { document: "a1b2", trusted: true }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "a1b2c3", trusted: true }) Drupe::Action::"Read"::request(input: { document: "a1b2c3", trusted: true }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "ab", trusted: false }) Drupe::Action::"Read"::request(input: { document: "ab", trusted: false }, 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): DENY

Provider Declarations

{
  "availableProviders": {
    "Strings::DigitCount": {
      "argumentTypes": [
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "count": {
            "paramType": "integer"
          }
        },
        "required": [
          "count"
        ]
      },
      "implementation": {
        "kind": "rhai",
        "script": "// `Strings::DigitCount(text) -> { count: Long }`.\n//\n// Counts digit characters via the `regex_count` host function. In the\n// policy the integer output is used inside arithmetic (`count + 1 <= 3`),\n// which only the unwrapped form allows.\nfn 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 #{ count: -1 };\n    }\n\n    #{ count: regex_count(\"[0-9]\", text) }\n}\n"
      }
    }
  }
}