Naming What Already Exists: PDP/PEP Was the Rule Pipeline Read Correctly

6 minute read

A team is evolving an agentic Trust framework. They have built it for some time. The framework computes verdicts; the verdicts gate actions; the system works.

Now they need to add safety to the picture. Trust × Safety, multiplicative composition, both required, neither sufficient. The architecture has to absorb the new dimension.

The instinct: we need a Policy Decision Point / Policy Enforcement Point split. Classical Zero Trust pattern. NIST SP 800-207. Let’s design the refactor.

The instinct is wrong.

The PDP/PEP split is not a refactor. It is the existing rule pipeline, named correctly. The seam was always there. The framework had it before anyone called it that.

This post is about the move that actually happened — not adding architecture, but recognising what was already in the codebase. The generalisable lesson: when a system has unnamed structure, name it before you extend it.

What PDP/PEP Says (Briefly)

The classical Zero Trust pattern, formalised in NIST SP 800-207 and earlier in XACML:

  • Policy Decision Point (PDP): the component that computes the verdict given the inputs. Identity, context, action, resource — in. Verdict — out. The PDP knows nothing about how the verdict will be enforced.
  • Policy Enforcement Point (PEP): the component that enforces the verdict at the boundary where the action would happen. Allow, deny, allow-with-conditions — applied to the actual transaction. The PEP knows nothing about how the verdict was computed.

The split is structural. The PDP can change without the PEP changing; the PEP can change without the PDP changing. They communicate through a typed verdict. This is the architectural pattern that made Zero Trust scalable across operating systems, network fabrics, and application platforms.

The pattern is good. The temptation, when adopting it for a new system, is to build it from scratch — design the PDP, design the PEP, wire them together, test the seam.

The Seam Was Already There

Here is what the codebase looked like before anyone said “PDP/PEP”:

PolicyEngine.evaluate(context) → Verdict
ActionGate.apply(verdict, action) → Result

The first call computes a verdict from context. The second call applies the verdict to a specific action. The two functions are already decoupled. The seam is between them.

The PDP is PolicyEngine.evaluate. The PEP is ActionGate.apply. They were not named that. They were named in terms of what they did locally. But structurally, the split was already in the architecture.

What had been missing was:

  • A name for the seam — PDP-side and PEP-side, separated by a typed verdict
  • An acknowledgement that advisory enforcement (run the verdict, log it, but don’t gate yet) was scattered across plugins and could be unified
  • A commitment that future evolutions of trust evaluation would land at the PDP-side, and future evolutions of action gating would land at the PEP-side

These three are the work. None of them is “build new architecture.” All of them are “name and extend what is there.”

What Trust × Safety Actually Did

The instinct was to attribute the PDP/PEP work to the Trust × Safety move — Trust × Safety forced us to do this refactor.

That story is wrong in a specific way. Trust × Safety did not create the need for PDP/PEP. The seam was already in the codebase. What Trust × Safety did was make the seam impossible to keep ignoring.

When trust was the only dimension, the unnamed seam was tolerable. The team could keep computing trust verdicts in one place and gating on them in another, without ever explicitly saying one is decision, the other is enforcement. The architecture worked.

When safety was added as an orthogonal dimension, the monolithic verdict computation collapsed. You cannot compute “Trust × Safety” as a single function from context to bit. The dimensions evaluate independently. They produce per-dimension verdicts. The composition is multiplicative. This means the verdict has structure, and the gating layer has to consume that structure rather than a single bit.

At that moment, the seam between computing the verdict and enforcing the verdict becomes load-bearing. The PDP-side computes a structured verdict over multiple dimensions; the PEP-side enforces that structured verdict against a specific action. The two cannot be tangled, because the structure of the verdict is what makes the multiplicative composition work.

Trust × Safety did not create the seam. It made the seam the place where the architecture’s correctness lives. The naming followed.

Why This Generalises

The pattern recurs in framework evolution: an integration introduces a new dimension; the dimension exposes structure that was always implicit but never named; the engineering work is naming, not building.

The diagnostic: when you find yourself reaching for a refactor that adds a new architectural layer, ask:

  • Is this layer already implicit somewhere in the codebase, decoupled by accident or by partial discipline?
  • If I name the implicit decoupling, do I need to add new code, or extend code that already exists?
  • Does naming the layer reduce my work-list, or grow it?

If naming reduces the work-list, the architecture was always there. The refactor isn’t a refactor; it’s a recognition. The engineering effort is in naming, communicating, and committing — not in building.

If naming grows the work-list (you discover you do need genuinely new code paths, new modules, new responsibilities), the integration is creating a new architectural element. That’s the harder case, and worth the explicit refactor.

The diagnostic catches a common over-architecting failure: when an integration looks like it needs a new layer, but actually exposes a layer that was already there with imperfect naming.

The 4-Phase Plan, Unchanged

Concretely, for the team in the example: the existing rule pipeline backlog (TD-54: rules + contexts; TD-55: plugin consolidation; TD-56: event pipeline unification) was already the work. Trust × Safety did not add a TD-57 “build PDP/PEP.” The PDP/PEP framing absorbed into the existing TDs:

  • TD-54 lands the rule pipeline ABCs and context holders — this is the PDP-side substrate
  • TD-55 consolidates the plugin layer — this rationalises advisory enforcement on the PEP-side
  • TD-56 unifies the event pipeline — this is the typed-verdict surface that connects PDP to PEP
  • A subsequent Phase 4 lift completes the PEP-side gating discipline

No new top-level technical-debt item. The reframe subtracts engineering work — by absorbing what looked like a new workstream into existing scope, correctly named. That subtraction is the quality signal. When a reframe shrinks the work-list, the reframe is right.

A team that adds a new TD for “PDP/PEP refactor” without checking the diagnostic will spend a quarter on architectural work that was always there. A team that names the existing seam will spend that quarter on the actual unfinished business — the rule pipeline that was already on the roadmap.

The Disposition

Most architecture work is naming. Most over-architecture comes from missing what is already there.

The instinct let’s design the PDP/PEP refactor sounds like rigour. It feels like doing the work. It produces design docs, sequence diagrams, integration plans. None of that is wrong on its face. The wrongness is upstream — the work was already mostly done, in code that nobody called by its proper name.

The discipline: when a new framework move surfaces, look for what is already there. If the implicit structure can be named, the naming is the work. If naming reduces scope, the diagnostic is confirming the architecture has been there all along; you are catching up to it rather than extending it.

The PDP/PEP framing didn’t add new infrastructure. It made the existing infrastructure legible. That is the highest-value architectural work that ever happens — and it is almost always under-credited because nothing visibly new shipped.

What shipped was the recognition. Recognition compounds. The next architectural move, on top of a correctly-named substrate, lands more cleanly than it would have on the unnamed one.

That is the work. Naming what already exists. Extending from there.