provider_enum_two_namespaces
The same Vendor::ThreatScan provider and Vendor::Severity enum as
provider_enum_severity_forbid, with an action schema that has actions in two
namespaces, App and Audit. One unscoped guardrail covers both:
@id("forbid_severe")
forbid (principal, action, resource) when {
Vendor::ThreatScan(context.input.document).severity
in [Vendor::Severity::"HIGH", Vendor::Severity::"CRITICAL"]
};
The enum is exported once, into the first namespace (App::Vendor_Severity), and
Audit’s actions refer to it by its full name. So the forbid fires on both namespaces:
@0App::Read"hello"→ ALLOW@10App::Read"malware here"→ DENY@20Audit::Inspect"hello"→ ALLOW@30Audit::Inspect"malware here"→ DENY
Referenced by guide/05-information-providers.md.
Policy
@id("permit_all")
permit (principal, action, resource);
// One unscoped guardrail for every action, in both namespaces.
@id("forbid_severe")
forbid (principal, action, resource)
when {
Vendor::ThreatScan(context.input.document).severity
in [Vendor::Severity::"HIGH", Vendor::Severity::"CRITICAL"]
};
Schema
namespace App {
type ReadInput = { document: String };
entity User;
entity Doc;
action "Read" appliesTo { principal: [User], resource: [Doc], context: { input: ReadInput } };
}
namespace Audit {
type ReadInput = { document: String };
entity User;
entity Doc;
action "Inspect" appliesTo { principal: [User], resource: [Doc], context: { input: ReadInput } };
}
Trace
@0 scope(principal: App::User::"alice", resource: App::Doc::"d1") request_context(input: { document: "hello" }) App::Action::"Read"::request(input: { document: "hello" }, callerPrincipal: App::User::"alice", callerResource: App::Doc::"d1", requestId: "u1")
@10 scope(principal: App::User::"alice", resource: App::Doc::"d1") request_context(input: { document: "malware here" }) App::Action::"Read"::request(input: { document: "malware here" }, callerPrincipal: App::User::"alice", callerResource: App::Doc::"d1", requestId: "u2")
@20 scope(principal: Audit::User::"bob", resource: Audit::Doc::"d2") request_context(input: { document: "hello" }) Audit::Action::"Inspect"::request(input: { document: "hello" }, callerPrincipal: Audit::User::"bob", callerResource: Audit::Doc::"d2", requestId: "u3")
@30 scope(principal: Audit::User::"bob", resource: Audit::Doc::"d2") request_context(input: { document: "malware here" }) Audit::Action::"Inspect"::request(input: { document: "malware here" }, callerPrincipal: Audit::User::"bob", callerResource: Audit::Doc::"d2", requestId: "u4")
Expected Output
@0 (time point 0): ALLOW [rules: 0]
@10 (time point 1): DENY [rules: 1]
@20 (time point 2): ALLOW [rules: 0]
@30 (time point 3): DENY [rules: 1]
Provider Declarations
{
"entityTypes": {
"Vendor::Severity": {
"enum": [
"LOW",
"MEDIUM",
"HIGH",
"CRITICAL"
]
}
},
"availableProviders": {
"Vendor::ThreatScan": {
"argumentTypes": [
{
"paramType": "string"
}
],
"outputType": {
"paramType": "record",
"fields": {
"severity": {
"paramType": "entity",
"entityType": "Vendor::Severity"
}
},
"required": [
"severity"
]
},
"implementation": {
"kind": "rhai",
"script": "// Vendor::ThreatScan(text) -> { severity: Vendor::Severity }.\n// Returns the bare enum id; Dogwood converts it to the enum entity and denies\n// the request if the id isn't one of the declared values.\nfn evaluate(text) {\n // A provider may be evaluated for ANY decision event, so the argument may\n // be absent (unit). Return the most restrictive value.\n if type_of(text) == \"()\" { return #{ severity: \"CRITICAL\" }; }\n let t = to_lower(text);\n if t.contains(\"ransomware\") { #{ severity: \"CRITICAL\" } }\n else if t.contains(\"malware\") { #{ severity: \"HIGH\" } }\n else if t.contains(\"scan\") { #{ severity: \"MEDIUM\" } }\n else if t.contains(\"garbled\") { #{ severity: \"SEVERE\" } } // not a declared value\n else { #{ severity: \"LOW\" } }\n}\n"
}
}
}
}