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:
@0—readmeis on the allowlist → ALLOW (left branch).@10—hiis 2 chars, solength < 4→ ALLOW (right branch).@20—longfilenameis neither allowlisted nor short → DENY.@30—manifestis 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"
}
}
}
}