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": ["secrets:**"],
"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: [secrets:**] max_tokens: 4000 rate_limit: 100
Intersect with BU Policy
Resources: [llm:openai/*] (unchanged) Denied: [secrets:**] (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: [secrets:**, 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": ["secrets:**", "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"] |
| max_length, max_items | Minimum | max_length:100 ∩ max_length:10 = max_length:10 |
| min_length, min_items | Maximum | min_items:1 ∩ min_items:3 = min_items:3 |
| type, pattern, list shorthand | Both apply | A value must satisfy both |
| validity | Overlap | Later not_before, earlier not_after |
The same constraint rules apply when policies from different levels (company, team, user, service) combine at request time. Resources differ: at request time, levels intersect directly, so a level that names no patterns in a domain allows nothing in it. Domain-aware inheritance applies only through extends.
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 | no llm: access | The child names llm:, and its pattern matches nothing the parent allows |
| llm:openai/* | tool:database/* | llm:openai/* | The parent allows nothing in tool:, so the child gets none; llm: is inherited |
The Security Invariant
Child ⊆ Parent always holds. A child policy can never grant access to resources the parent doesn't allow. In each domain the child names, it keeps only what both its patterns and the parent's allow. A child pattern outside the parent's scope matches nothing.
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: A Time-Bounded Policy
For emergency access, bind the person to a policy that holds only for the window, through their identity provider group. Outside the window the policy refuses every call, and like every policy it can only narrow: admin:** must already be allowed at the company or BU level.
{
"policy_id": "team:incident-response",
"validity": {
"not_before": "2025-01-17T09:00:00Z",
"not_after": "2025-01-17T17:00:00Z"
},
"resources": ["admin:**"],
"attestations": ["incident_approval"],
"constraints": {
"rate_limit": 10,
"attestations": {
"incident_approval": {"approval_criteria": "role:incident-commander", "timeout": 300}
}
}
}