provider_regex_analyze_fields
Several calls to the same Regex::Analyze provider, each projecting a
different output field and comparing it, combined with && as plain Cedar
(the unwrapped provider form — no guardrails { … } block). Permit Read
only when the document:
- starts with an uppercase letter —
^[A-Z]→.is_match == true - has at least three digit characters —
[0-9]→.count >= 3 - whose first run of digits is exactly
42—[0-9]+→.first_match == "42"
Regex::Analyze(string, string) -> { is_match: bool, first_match: string, count: integer } is declared in providers.json. Its Rhai script is
inlined into providers.json (as the "script" field) rather than
referenced via scriptFile, because the dogwood CLI parses the
declarations text directly and does not resolve external scriptFile
references.
Trace
Each denial isolates one failing condition:
| doc | starts A–Z | ≥3 digits | first run == “42” | verdict |
|---|---|---|---|---|
Abc42x999 | yes | yes (5) | yes | ALLOW |
abc42x999 | no | yes | yes | DENY |
A42 | yes | no (2) | yes | DENY |
A99942 | yes | yes | no (99942) | DENY |
Run from this directory so the schema/providers paths resolve:
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
Lifted from corpus case 0005_regex_operations.
Referenced by guide/05-information-providers.md.
Policy
// Permit Read only when the document starts with an uppercase letter, has at
// least three digit characters, and its first run of digits is exactly "42".
// Three calls to one Regex::Analyze provider, each projecting a different
// output field, combined with && as plain Cedar.
@id("read_regex_analyze")
permit (
principal,
action == Drupe::Action::"Read",
resource
)
when {
Regex::Analyze(context.input.document, "^[A-Z]").is_match == true
&& Regex::Analyze(context.input.document, "[0-9]").count >= 3
&& Regex::Analyze(context.input.document, "[0-9]+").first_match == "42"
};
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: "Abc42x999" }) Drupe::Action::"Read"::request(input: { document: "Abc42x999" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "abc42x999" }) Drupe::Action::"Read"::request(input: { document: "abc42x999" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@20 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "A42" }) Drupe::Action::"Read"::request(input: { document: "A42" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
@30 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { document: "A99942" }) Drupe::Action::"Read"::request(input: { document: "A99942" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u4")
Expected Output
@0 (time point 0): ALLOW [rules: 0]
@10 (time point 1): DENY
@20 (time point 2): DENY
@30 (time point 3): DENY
Provider Declarations
{
"availableProviders": {
"Regex::Analyze": {
"argumentTypes": [
{
"paramType": "string"
},
{
"paramType": "string"
}
],
"outputType": {
"paramType": "record",
"fields": {
"is_match": {
"paramType": "bool"
},
"first_match": {
"paramType": "string"
},
"count": {
"paramType": "integer"
}
},
"required": [
"is_match",
"first_match",
"count"
]
},
"implementation": {
"kind": "rhai",
"script": "// `Regex::Analyze(text, pattern) -> { is_match, first_match, count }`.\n//\n// One provider that exercises ALL THREE regex host functions Dogwood\n// exposes to provider scripts:\n// * regex_is_match(pattern, text) -> bool \u2014 does the pattern match?\n// * regex_find(pattern, text) -> string \u2014 the first match (\"\" if none)\n// * regex_count(pattern, text) -> i64 \u2014 number of matches\n//\n// The output record surfaces each result so the policy can project and\n// compare them independently.\nfn evaluate(text, pattern) {\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) == \"()\" || type_of(pattern) == \"()\" {\n return #{ is_match: false, first_match: \"\", count: -1 };\n }\n\n #{\n is_match: regex_is_match(pattern, text),\n first_match: regex_find(pattern, text),\n count: regex_count(pattern, text),\n }\n}\n"
}
}
}
}