Docs/Policies/Inheritance

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:

LevelPolicy ID PrefixDescription
Companycompany:acmeOrganization-wide defaults
Business Unitbu:financeDepartment-level restrictions
Teamteam:analystsTeam-specific rules
Caller (user)user:aliceHuman user policies
Caller (app)app:report-serviceService/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:

ValueMeaningUse When
resources: []Defer to parent — parent's resources applyYou have no additional restrictions at this level
resources: ["**"]Explicit pass-through — same as [] but clearer intentYou want to document "no restrictions here" explicitly
resources: ["patterns..."]Restrict within parent's scopeYou want to narrow what's allowed
(field absent)Same as [] — defer to parentShorthand, 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.

Example: Defer to Parent
company:  resources: ["llm:openai/*"]
team:     resources: []

// Team defers → gets ["llm:openai/*"]
// Team operates within company's scope
Example: Restrict Further
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:

Level 1: Company Policy
{
  "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}
      }
    }
  }
}
Level 2: Business Unit Policy
{
  "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}
      }
    }
  }
}
Level 3: Caller Policy (user or app)
{
  "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):

1

Start with Company Policy

Resources: [llm:openai/*]
Denied: [*.secret, *.password]
max_tokens: 4000
rate_limit: 100
2

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!
3

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

Caller's 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:

FieldMerge StrategyExample
resourcesDomain-Aware Intersection[llm:*] ∩ [llm:openai/*] = [llm:openai/*]
denied_resourcesUnion[X] ∪ [Y] = [X, Y]
maxMinimummax:4000 ∩ max:500 = max:500
minMaximummin:0 ∩ min:10 = min:10
rangeIntersection[0,2000] ∩ [0,500] = [0,500]
allowed_valuesIntersection["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:

Parent: BU Finance
"resources": [
  "finance:*",
  "tool:calculator",
  "tool:analyzer",
  "report:*"
]
Child: Trading Team
"extends": "bu:finance",
"resources": [
  "finance:trading/*",
  "finance:positions/*"
]
// Note: No tool: or report: specified!
Result: Trading Team Effective Policy
"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 HasChild SpecifiesResultWhy
llm:openai/*llm:openai/gpt-4llm:openai/gpt-4Valid restriction (within scope)
llm:openai/*llm:anthropic/claudellm: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" }
}

Related Topics