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 <= 3 ⇔ count <= 2):
| trusted | document | digits | verdict |
|---|---|---|---|
| true | ab | 0 | ALLOW |
| true | a1b2 | 2 | ALLOW |
| true | a1b2c3 | 3 | DENY |
| false | ab | 0 | DENY |
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"
}
}
}
}