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_risk_decimal_method

The decimal-extension-method comparison form. The Content::Risk provider returns a record whose severityScore field is a Cedar decimal, so the policy compares it with the decimal extension method .lessThan(decimal("0.5")) rather than the bare < operator (Cedar decimals are not comparable with <).

providers.json declares Content::Risk(string) -> { severityScore: decimal }. The Rhai script is inlined into providers.json (as script) rather than referenced via scriptFile, because the dogwood CLI reads the providers file as raw text and does not resolve scriptFile paths. The equivalent standalone risk.rhai is included for readability. It returns a fixed score per keyword: "safe" -> 0.10, "spam" -> 0.80, anything else -> 0.50.

The trace shows all three cases:

  • @0document: "safe" (score 0.10, below 0.5) -> ALLOW.
  • @10document: "spam" (score 0.80) -> DENY.
  • @20document: "other" (score 0.50, not less than 0.5) -> DENY.

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

Policy

// Permit Read only when the content-risk score is below 0.5, as decided by the
// Content::Risk provider. The output is a Cedar decimal, so the comparison uses
// the decimal extension method .lessThan(decimal("...")) rather than <.
@id("read_low_risk")
permit (
    principal,
    action == Drupe::Action::"Read",
    resource
)
when {
    Content::Risk(context.input.document).severityScore.lessThan(decimal("0.5"))
};

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: "safe" }) Drupe::Action::"Read"::request(input: { document: "safe" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "spam" }) Drupe::Action::"Read"::request(input: { document: "spam" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "other" }) Drupe::Action::"Read"::request(input: { document: "other" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")

Expected Output

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

Provider Declarations

{
  "availableProviders": {
    "Content::Risk": {
      "argumentTypes": [
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "severityScore": {
            "paramType": "decimal"
          }
        },
        "required": [
          "severityScore"
        ]
      },
      "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 #{ severityScore: parse_decimal(\"-1.0\") };\n    }\n\n    let score = if text == \"safe\" {\n        parse_decimal(\"0.10\")\n    } else if text == \"spam\" {\n        parse_decimal(\"0.80\")\n    } else {\n        parse_decimal(\"0.50\")\n    };\n    #{ severityScore: score }\n}\n"
      }
    }
  }
}