The Policy Execution Pipeline: Phases, Failure Modes, and Short-Circuits
A request enters the gateway. Policies execute. A response exits.
What happens in between? Hexarch models this as a pipeline with distinct phases, ordered execution, and explicit failure semantics.
The Pipeline Phases
The PolicyPhase enum defines three stages:
export enum PolicyPhase {
PRE_REQUEST = 'Pre-Request',
POST_REQUEST = 'Post-Request',
ERROR = 'Error Handling'
}
Pre-Request: Security checks, rate limiting, request transformation. If a policy denies here, the request never reaches the backend.
Post-Request: Response transformation, PII masking, caching. The backend has responded; these policies modify what the client sees.
Error: Terminal error handling. When something goes wrong, these policies decide what the client sees.
The Policy Interface
Each policy specifies its position in the pipeline:
interface Policy {
id: string;
name: string;
type: PolicyType;
description: string;
enabled: boolean;
scope: PolicyScope;
// Pipeline position
phase: PolicyPhase;
order: number;
// Failure behavior
failureMode: FailureMode;
shortCircuit: boolean;
// Scope binding
targetRef?: PolicyTargetRef;
// Type-specific settings
config: any;
}
The order field determines execution sequence within a phase. Policy 1 runs before policy 2.
Failure Modes
The FailureMode enum defines what happens when a policy can’t execute:
export enum FailureMode {
FAIL_OPEN = 'Fail Open',
FAIL_CLOSED = 'Fail Closed'
}
Fail-Open: The policy is advisory. If it can’t run (timeout, exception), the request proceeds without it. Use for caching, analytics, non-critical transformations.
Fail-Closed: The policy is mandatory. If it can’t run, the request fails. Use for authentication, authorization, compliance controls.
The default for security policies is always FAIL_CLOSED. If the auth check can’t run, you don’t assume the request is authorized.
Short-Circuit Behavior
The shortCircuit flag enables early exit:
interface Policy {
shortCircuit: boolean; // If true, stop processing on match
}
Example: Edge caching policy with shortCircuit: true. If the cache has the response, return it immediately—don’t execute remaining policies or call the backend.
In the UI, short-circuit policies show a special badge:
{policy.shortCircuit && (
<Badge variant="warning">Short-circuits</Badge>
)}
The ExecutionFlowView
The Policies.tsx component includes an ExecutionFlowView that visualizes the pipeline:
function ExecutionFlowView({ policies }: { policies: Policy[] }) {
const policiesByPhase = useMemo(() => ({
PRE_REQUEST: policies
.filter(p => p.phase === 'PRE_REQUEST')
.sort((a, b) => a.order - b.order),
POST_REQUEST: policies
.filter(p => p.phase === 'POST_REQUEST')
.sort((a, b) => a.order - b.order),
ERROR: policies
.filter(p => p.phase === 'ERROR')
.sort((a, b) => a.order - b.order)
}), [policies]);
return (
<div className="execution-flow">
<Stage label="Inbound" />
<PhaseColumn phase="PRE_REQUEST" policies={policiesByPhase.PRE_REQUEST} />
<Stage label="Target" />
<PhaseColumn phase="POST_REQUEST" policies={policiesByPhase.POST_REQUEST} />
<Stage label="Outbound" />
<PhaseColumn phase="ERROR" policies={policiesByPhase.ERROR} />
</div>
);
}
The visual representation: Inbound → Pre-Request → Target → Post-Request → Outbound, with Error handling as a separate branch.
Policy Types
The PolicyType enum categorizes behavior:
export enum PolicyType {
SECURITY = 'Security',
TRANSFORMATION = 'Transformation',
TRAFFIC = 'Traffic Control',
MEDIATION = 'Protocol Mediation',
CUSTOM = 'Custom Extension'
}
In the current dashboard demo, policy cards also pick type-specific icons/classes:
<div
className={`policy-icon ${
policy.type === PolicyType.SECURITY
? 'policy-icon--security'
: policy.type === PolicyType.TRAFFIC
? 'policy-icon--traffic'
: 'policy-icon--transform'
}`}
>
<i className={`fas ${
policy.type === PolicyType.SECURITY
? 'fa-lock-shield'
: policy.type === PolicyType.TRAFFIC
? 'fa-bolt'
: 'fa-shuffle'
}`} />
</div>
Scope Binding
The PolicyScope enum defines where a policy applies:
export enum PolicyScope {
GLOBAL = 'Global',
API = 'API',
VERSION = 'Version',
PLAN = 'Plan',
ROUTE = 'Route'
}
More specific scopes override less specific ones. A route-level rate limit overrides an API-level rate limit for that route.
When scope isn’t GLOBAL, the policy includes a targetRef:
interface PolicyTargetRef {
type: PolicyScope;
id: string;
}
Example: The Initial Policy Set
The app ships with demo policies:
const INITIAL_POLICIES: Policy[] = [
{
id: 'p1',
name: 'OAuth2 Introspection',
type: PolicyType.SECURITY,
description: 'Validates bearer tokens against an external OAuth2 server.',
enabled: true,
phase: PolicyPhase.PRE_REQUEST,
order: 1,
failureMode: FailureMode.FAIL_CLOSED,
scope: PolicyScope.GLOBAL,
shortCircuit: false,
config: { timeout: 500, introspectionEndpoint: '...' }
},
{
id: 'p2',
name: 'Edge Cache',
type: PolicyType.TRAFFIC,
description: 'Serves cached responses to reduce backend load.',
enabled: true,
phase: PolicyPhase.PRE_REQUEST,
order: 0,
failureMode: FailureMode.FAIL_OPEN,
shortCircuit: true,
scope: PolicyScope.API,
targetRef: { type: PolicyScope.API, id: 'a1' },
config: { ttl: 300, keyPattern: 'uri' }
},
{
id: 'p3',
name: 'Spike Arrest',
type: PolicyType.TRAFFIC,
description: 'Protects backend from sudden traffic surges.',
enabled: true,
phase: PolicyPhase.PRE_REQUEST,
order: 2,
failureMode: FailureMode.FAIL_CLOSED,
shortCircuit: false,
scope: PolicyScope.API,
targetRef: { type: PolicyScope.API, id: 'a1' },
config: { ratePerMinute: 1000, burstBuffer: 50 }
},
{
id: 'p4',
name: 'Sensitive Data Mask',
type: PolicyType.TRANSFORMATION,
description: 'Scrubs PII from outbound response payloads.',
enabled: true,
phase: PolicyPhase.POST_REQUEST,
order: 0,
failureMode: FailureMode.FAIL_CLOSED,
shortCircuit: false,
scope: PolicyScope.GLOBAL,
config: { patterns: ['creditCard', 'ssn'] }
}
];
Execution order for a request to API a1:
- OAuth2 Introspection (security check)
- Edge Cache (if hit, short-circuit—return cached response)
- Spike Arrest (if over limit, deny)
- → Backend call
- Sensitive Data Mask (scrub PII from response)
- → Return to client
The Execution Guarantee
When Hexarch evaluates a request:
- Policies are sorted by phase, then by order
- Each policy executes in sequence
- If
shortCircuitis true and the policy matches, stop - If a policy fails and
failureModeisFAIL_CLOSED, terminate - If a policy fails and
failureModeisFAIL_OPEN, skip and continue
This is deterministic. Given the same request and policy set, you get the same execution path.
Try It
The Policies page at /policies offers two views:
- Grid View: Browse policies as cards, see type/phase/scope at a glance
- Flow View: See the execution pipeline with policies positioned by phase
Toggle between them to understand both the policy catalog and the execution model.