Hierarchical Inheritance
MAPL policies inherit and compose hierarchically using intersection semantics. At each level, constraints can only become stricter—no privilege escalation is possible.
Policy Hierarchy Levels
MAPL supports a multi-level hierarchy. The primary levels you'll work with are:
| Level | Policy ID Prefix | Description |
|---|---|---|
| Company | company:acme | Organization-wide defaults |
| Business Unit | bu:finance | Department-level restrictions |
| Team | team:analysts | Team-specific rules |
| Caller (user) | user:alice | Human user policies |
| Caller (app) | app:report-service | Service/agent policies |
Caller = User or App
The bottom level of the hierarchy is the caller — whoever is making the request. This can be a human user (user:alice) or an automated service (app:report-service). Both inherit from the same team/BU/company hierarchy.
Resource Field Semantics
The resources field has specific semantics that ensure monotonic restriction through the hierarchy:
| Value | Meaning | Use When |
|---|---|---|
| resources: [] | Defer to parent — parent's resources apply | You have no additional restrictions at this level |
| resources: ["**"] | Explicit pass-through — same as [] but clearer intent | You want to document "no restrictions here" explicitly |
| resources: ["patterns..."] | Restrict within parent's scope | You want to narrow what's allowed |
| (field absent) | Same as [] — defer to parent | Shorthand, but less explicit |
Common Misconception
resources: [] does NOT mean "no access" or "deny all". It means "I defer to my parent's policy." The child still operates within the parent's allowed scope — it just doesn't add further restrictions at this level.
company: resources: ["llm:openai/*"] team: resources: [] // Team defers → gets ["llm:openai/*"] // Team operates within company's scope
company: resources: ["llm:openai/*"] team: resources: ["llm:openai/gpt-4"] // Team restricts → gets ["llm:openai/gpt-4"] // Only GPT-4, not all OpenAI models
Why Inheritance?
Without Inheritance
1,000 users × 100 resources = 100,000 rules to manage
Every user policy must repeat common rules. Changes require updating every policy.
With Inheritance
log(1,000) ≈ 3-4 levels + 100 resources = ~100 policies
Define rules once at the org level, specialize at team and user levels.
Example Inheritance Chain
Let's build a 3-level hierarchy for a financial services company:
{
"policy_id": "company:FinTech",
"description": "Company-wide base policy",
"resources": ["llm:openai/*"],
"denied_resources": ["*.secret", "*.password"],
"constraints": {
"rate_limit": 100,
"parameters": {
"llm:openai/chat.completions": {
"max_tokens": {"max": 4000}
}
}
}
}{
"policy_id": "bu:Analytics",
"extends": "company:FinTech",
"description": "Analytics BU - enforces deterministic results",
"constraints": {
"rate_limit": 50,
"parameters": {
"llm:openai/chat.completions": {
"max_tokens": {"max": 2000},
"temperature": {"max": 0.3}
}
}
}
}{
"policy_id": "user:alice", // or "app:report-service"
"extends": "bu:Analytics",
"description": "Alice - Analyst (or automated service)",
"resources": ["llm:openai/chat.completions"],
"constraints": {
"rate_limit": 10,
"parameters": {
"llm:openai/chat.completions": {
"model": ["gpt-3.5-turbo"],
"max_tokens": {"max": 500}
}
}
},
"denied_resources": ["data:executive/*"]
}Step-by-Step Resolution
When a caller makes a request, MAPL resolves the policy chain company:FinTech → bu:Analytics → caller (where caller is user:alice or app:service):
Start with Company Policy
Resources: [llm:openai/*] Denied: [*.secret, *.password] max_tokens: 4000 rate_limit: 100
Intersect with BU Policy
Resources: [llm:openai/*] (unchanged) Denied: [*.secret, *.password] (unchanged) max_tokens: min(4000, 2000) = 2000 ← More restrictive! temperature: 0.3 ← New constraint added rate_limit: min(100, 50) = 50 ← More restrictive!
Intersect with Caller Policy
Resources: [llm:openai/chat.completions] ← More specific! Denied: [*.secret, *.password, data:executive/*] ← Caller adds max_tokens: min(2000, 500) = 500 ← Most restrictive! temperature: 0.3 (Caller didn't override) model: [gpt-3.5-turbo] ← Caller adds restriction rate_limit: min(50, 10) = 10 ← Most restrictive!
Final Effective Policy
{
"resources": ["llm:openai/chat.completions"],
"denied_resources": ["*.secret", "*.password", "data:executive/*"],
"constraints": {
"rate_limit": 10,
"parameters": {
"llm:openai/chat.completions": {
"model": ["gpt-3.5-turbo"],
"max_tokens": {"max": 500},
"temperature": {"max": 0.3}
}
}
}
}Intersection Rules
Here's how different fields merge during inheritance:
| Field | Merge Strategy | Example |
|---|---|---|
| resources | Domain-Aware Intersection | [llm:*] ∩ [llm:openai/*] = [llm:openai/*] |
| denied_resources | Union | [X] ∪ [Y] = [X, Y] |
| max | Minimum | max:4000 ∩ max:500 = max:500 |
| min | Maximum | min:0 ∩ min:10 = min:10 |
| range | Intersection | [0,2000] ∩ [0,500] = [0,500] |
| allowed_values | Intersection | ["A","B","C"] ∩ ["B","C"] = ["B","C"] |
Key Principle: Most Restrictive Wins
- • Policies always get MORE restrictive down the hierarchy
- • You can't accidentally grant more permissions by inheriting
- • Denials always propagate (no overrides!)
Domain-Aware Resource Inheritance
Resource inheritance uses domain-aware logic—if a child doesn't mention a domain, it automatically inherits the parent's resources for that domain:
"resources": [ "finance:*", "tool:calculator", "tool:analyzer", "report:*" ]
"extends": "bu:finance", "resources": [ "finance:trading/*", "finance:positions/*" ] // Note: No tool: or report: specified!
"resources": [ "finance:trading/*", // ← Restricted from finance:* "finance:positions/*", // ← Additional restriction "tool:calculator", // ← Inherited (child didn't mention tool:) "tool:analyzer", // ← Inherited (child didn't mention tool:) "report:*" // ← Inherited (child didn't mention report:) ]
This is what enables O(log M + N) complexity—children don't need to re-specify everything they inherit!
Monotonic Restriction Guarantee
Child patterns are validated against parent's scope. You can only restrict within what the parent allows — never expand beyond it.
| Parent Has | Child Specifies | Result | Why |
|---|---|---|---|
| llm:openai/* | llm:openai/gpt-4 | llm:openai/gpt-4 | Valid restriction (within scope) |
| llm:openai/* | llm:anthropic/claude | llm:openai/* | Rejected (outside scope) — parent preserved |
| llm:openai/* | tool:database/* | llm:openai/* | Rejected (new domain) — dropped entirely |
The Security Invariant
Child ⊆ Parent always holds. A child policy can never grant access to resources the parent doesn't allow. This is enforced mathematically — patterns outside the parent's scope are silently dropped, and the parent's pattern is preserved.
Why No Overrides?
MAPL explicitly prohibits overrides. Why?
Provable Security
Mathematical theorems (Monotonic Restriction, Transitive Denial) don't hold with overrides.
Audit Integrity
If policies can be overridden, you can't trust the policy hierarchy for compliance.
No Escalation
Overrides create paths to bypass restrictions and escalate privileges.
Instead: Time-Bounded Groups
For emergency access, use time-bounded group membership with enhanced auditing:
{
"policy_id": "group:emergency-access",
"validity": {
"not_before": "2025-01-17T09:00:00Z",
"not_after": "2025-01-17T17:00:00Z"
},
"resources": ["admin:**"],
"constraints": { "audit_level": "maximum" }
}