PolicyFlow

Architecture and Advanced Topics

Understanding PolicyFlow's architecture and evaluation model

This section covers the architectural concepts behind PolicyFlow, including policy composition, evaluation flow, conflict resolution, and performance considerations.

Policy Composition

PolicyFlow's architecture is built on the principle of policy composition - multiple independent policies work together to make authorization decisions.

The Composition Model

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Universal      │     │  Conditional    │     │  Conditional    │
│  Policy A       │     │  Policy B       │     │  Policy C       │
│  (All requests) │     │  (EU users)     │     │  (Financial)    │
└────────┬────────┘     └────────┬────────┘     └────────┬────────┘
         │                       │                       │
         └───────────────────────┴───────────────────────┘

                         ┌───────▼────────┐
                         │ Policy Engine  │
                         │ (Evaluation &  │
                         │  Resolution)   │
                         └───────┬────────┘

                         ┌───────▼────────┐
                         │ Final Decision │
                         │ ALLOW / DENY   │
                         └────────────────┘

Benefits of Composition

  1. Separation of Concerns: Each policy handles a specific aspect
  2. Reusability: Policies can be shared across applications
  3. Maintainability: Smaller, focused policies are easier to understand
  4. Scalability: New policies can be added without modifying existing ones

Evaluation Flow

The PolicyFlow engine follows a systematic evaluation process:

1. Request Receipt

The engine receives an authorization request containing:

  • User: The principal performing the action
  • Resource: The object being accessed
  • Action: The operation being attempted
  • Context: Additional request information

2. Policy Selection

The engine filters all loaded policies:

// Step 1: Type matching
for each policy in all_policies:
    if policy.schemas.User matches request.user.type
    AND policy.schemas.Resource matches request.resource.type
    AND policy.schemas.Context matches request.context.type:
        candidate_policies.add(policy)

// Step 2: Where clause evaluation
for each policy in candidate_policies:
    if policy.schemas.where_conditions.evaluate(request):
        selected_policies.add(policy)

// Step 3: Action filtering (if specified)
for each policy in selected_policies:
    if policy.actions is empty OR request.action matches policy.actions:
        policies_to_evaluate.add(policy)

3. Rule Evaluation

For each selected policy, the engine determines a decision. This pseudocode illustrates the logic for a single policy evaluation.

function evaluate_policy(policy, request):
    let allow_found = false

    // Evaluate rules in priority order (lower number first)
    rules_sorted = policy.rules.sortBy(r => r.priority ?? 5000)

    for each rule in rules_sorted:
        if rule.when.evaluate(request):
            decision = rule.then  // ALLOW or DENY

            // Deny-Overrides: If any rule denies, the policy's decision is DENY.
            if decision == DENY:
                return DENY

            if decision == ALLOW:
                allow_found = true
                // Continue evaluating to check for a subsequent DENY

    // If we finished the loop without finding a DENY:
    if allow_found:
        return ALLOW

    // If no rules matched, return DENY (secure by default)
    return DENY

4. Final Resolution

After evaluating all applicable policies, the engine combines the results using a strict Deny-Overrides strategy.

function resolve_all_decisions(policy_decisions):
    // If ANY policy returned DENY, the final decision is DENY.
    if policy_decisions.any(d => d == DENY):
        return DENY

    // If at least one policy returned ALLOW and none returned DENY.
    if policy_decisions.any(d => d == ALLOW):
        return ALLOW

    // If no policies were selected to be evaluated, deny by default.
    return DENY

Conflict Resolution Strategy

PolicyFlow uses a strict Deny-Overrides strategy: if ANY rule or policy returns DENY, the request is denied.

Priority System

Priorities control evaluation order only, not decision outcomes:

PriorityDescriptionPurpose
0-999Evaluated firstQuick security checks, fail fast
1000-2999Early evaluationCompliance checks
3000-4999Mid evaluationBusiness logic
5000Default priorityStandard rules
5001-7999Later evaluationAdditional checks
8000-10000Evaluated lastExpensive operations

Key Principles

  1. Deny Always Wins: One DENY anywhere = request denied
  2. Priority is for Performance: Lower priority rules are evaluated first to fail fast
  3. No Override Mechanism: Priority cannot make an ALLOW override a DENY

Resolution Examples

Example 1: Priority Affects Evaluation Order

// Policy A
rule QuickSecurityCheck {
    when user.isSuspended
    then DENY
    priority: 100  // Evaluated first - fail fast
}

// Policy B
rule ExpensiveOwnerCheck {
    when {
        // Complex ownership validation
        const isOwner = complexOwnershipCheck(user, resource);
        return isOwner;
    }
    then ALLOW
    priority: 9000  // Evaluated last - expensive
}

// If user is suspended, QuickSecurityCheck denies immediately
// ExpensiveOwnerCheck is never evaluated (performance optimization)

Example 2: Deny Always Wins

// Policy A
rule AdminFullAccess {
    when "admin" in user.roles
    then ALLOW
    priority: 0  // Highest priority (evaluated first)
}

// Policy B
rule ComplianceRestriction {
    when resource.underAudit == true
    then DENY
    priority: 10000  // Lowest priority (evaluated last)
}

// Result: DENY
// Even though AdminFullAccess has priority 0 and ComplianceRestriction has
// priority 10000, the DENY wins. Priority only controlled evaluation order.

Performance Optimization

1. Policy Selection Optimization

The most critical optimization is reducing the number of policies evaluated:

// Indexed attributes for fast filtering
policy OptimizedPolicy {
    schemas {
        // These attributes should be indexed:
        User from Auth.User where user.isActive == true
        Resource from Data.Resource where resource.type == "document"
        Context from Web.Context where context.environment == "production"
    }
}

2. Rule Ordering

Use priority to optimize evaluation order:

rules {
    // Quick denials first (priority 1000) - fail fast
    rule DenyInactive {
        when !user.isActive
        then DENY
        priority: 1000
    }

    // Common allows next (priority 5000 - default)
    rule PublicReadAccess {
        when resource.isPublic AND action == "read"
        then ALLOW
    }

    // Expensive checks last (priority 8000)
    rule ComplexOwnershipCheck {
        when {
            // Complex computation
            const ownership = calculateOwnershipChain(user, resource);
            return ownership.isValid;
        }
        then ALLOW
        priority: 8000
    }
}

// Performance benefit: If user is inactive, we deny immediately
// without running the expensive ownership calculation

3. Caching Strategy

// Cache-friendly policy design
policy CacheablePolicy {
    // Use stable attributes for caching
    schemas {
        User from Auth.User where user.accountType in ["basic", "premium"]
        Resource from Data.Resource where resource.cacheKey != null
    }

    rules {
        // Avoid time-based conditions in cached policies
        rule StableRule {
            when user.subscription == "active" AND resource.isPublished
            then ALLOW
        }
    }
}

// Non-cacheable policy (separate)
policy TimeBasedPolicy {
    rules {
        rule TimeCheck {
            when {
                const hour = DateTime.Now().ToUnit(TimeUnit.Hours)
                return hour >= 9 AND hour < 17
            }
            then ALLOW
        }
    }
}

4. Relationship Query Optimization

// Efficient: Check existence
when Relationships.Has(user, "member_of", resource.team)
then ALLOW

// Less efficient: Retrieve all relations
when {
    const teams = Relationships.GetRelated(user, "member_of")
    return resource.team in teams
}
then ALLOW

// Optimize path queries
when Relationships.PathExists(user, "manages", resource.owner)
then ALLOW

Deployment Architecture

Typical Deployment

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Application   │────►│  Policy         │────►│   Policy        │
│   (PEP)         │     │  Decision       │     │   Information   │
│                 │◄────│  Point (PDP)    │◄────│   Point (PIP)   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                               │                          │
                               ▼                          ▼
                        ┌─────────────────┐      ┌─────────────────┐
                        │ PolicyFlow      │      │ - User Service  │
                        │ Engine          │      │ - Resource DB   │
                        │                 │      │ - Context API   │
                        │ - Policy Cache  │      └─────────────────┘
                        │ - Rule Engine   │
                        │ - Decision Log  │
                        └─────────────────┘

Scaling Considerations

  1. Horizontal Scaling: PolicyFlow engines are stateless and can be scaled horizontally
  2. Policy Distribution: Use a policy repository with versioning
  3. Caching Layer: Implement decision caching for frequently repeated requests
  4. Monitoring: Track policy evaluation times and cache hit rates

Advanced Patterns

1. Dynamic Policy Loading

// Policy with feature flags
policy FeatureAccess {
    rules {
        rule BetaFeature {
            when env["BETA_ENABLED"] == true
                AND "beta_tester" in user.roles
            then ALLOW
        }
    }
}

2. Multi-Tenant Policies

policy TenantIsolation {
    schemas {
        User from Auth.User
        Resource from Data.Resource where resource.tenantId != null
    }

    rules {
        rule SameTenantOnly {
            when user.tenantId != resource.tenantId
            then DENY
            priority: 0  // Check first for performance
            reason: "Cross-tenant access prohibited"
        }
    }
}

Next Steps