The hand-offs: extract, govern, evaluate, expose
Objective
Explain the hand-off at each seam of the pipeline (extraction to Git, Git to
signed bundle, bundle to PDP, PDP to MCP query) and author the allow rules for
the policy the fabric enforces.
Concept
An architect's job is the seams, not the boxes. Each hand-off is where a bad assumption causes an outage or a leak, so name what crosses each one.
Seam 1: application code to Rego in Git (Zift)
Zift's two-pass scan finds authorization scattered across handlers and
middleware and zift extract rewrites it as a single default-deny Rego policy.
In the lab, three embedded checks became three parts of the policy:
- a
requireRole(...)middleware became therole_permissionsmap, - an inline
if rec.owner_id == user.idguard became the ownership rule, - and a classification check on restricted records became the restricted path.
What crosses the seam is intent: the app stops deciding and starts asking. The externalization percentage Zift reports is how much of the app's authorization now lives in this file rather than in code.
Seam 2: Git to signed bundle (Writ)
The extracted policy is reviewed and merged like any code. On merge, Writ
auto-builds it into a signed bundle and scopes it in the entity hierarchy
(tenant, org, system, app). What crosses this seam is a chain of custody:
reviewed in Git, built and signed by Writ, verifiable by the PDP before it
loads. Writ then promotes the same signed bytes dev to staging to prod;
rollback redeploys a prior commit_sha.
Seam 3: bundle to PDP (OPA / Enterprise OPA)
The PDP pulls the bundle, verifies the signature, and evaluates the policy at
the PEP on every request. This is the only stage on the request path. What
crosses the seam is a decision: input in, allow or deny out, under a
latency budget (roughly sub-5ms to under 50ms depending on topology).
Seam 4: PDP to MCP query (Herald)
Herald exposes the same authorization query over MCP so callers, including AI
agents, can ask "is this allowed?" and get a decision. Herald federates context
from systems of record into input: in the lab, the HRIS employment_status
and the ITSM change_ticket.status. What crosses this seam is context: the
signals the policy needs but the caller does not hold. Herald fronts the query;
the PDP still decides.
Hands-on lab
Now author the rules. Open ../lab/policy.rego and
implement the TODO(learner) block: three allow if { ... } rules that OR
together on top of default allow := false. Every rule requires an active
employee (input.subject.employment_status == "active", the HRIS signal from
seam 4).
- Role + tenancy — the role permits the action
(
input.action in role_permissions[input.subject.role], the map from seam 1), same org (input.subject.org == input.resource.org), resource below restricted (input.resource.classification in {"public", "internal"}). - Ownership — action
readorwriteandinput.resource.owner_id == input.subject.id, regardless of org. No delete on this path. - Restricted — like rule 1 but for
input.resource.classification == "restricted", additionally requiringinput.context.change_ticket.status == "approved"(the ITSM signal from seam 4).
Run until green:
git clone https://github.com/EnforceAuth/university-labs.git
cd university-labs
opa test courses/capstone-four-product-fabric -v
You move from FAIL: 3/11 to PASS: 11/11. The three deny tests that were
already passing are your guardrail: if a rule you write lets an editor act
across orgs, or lets a viewer delete, or lets a restricted read through without
approval, a deny test flips to fail. That is the exact check a Writ promotion
gate would run before signing a bundle. Keep iterating until opa test is green.
Check for understanding
- Name what crosses each of the four seams in one word (intent, custody, decision, context) and say which product owns each seam.
- Herald federates
employment_statusandchange_ticket.statusintoinput. Which rule in your policy depends on each, and what happens to the decision if Herald cannot reach the ITSM source and the status is absent? - Your ownership rule must not grant
delete. Which deny test enforces that, and why is delete kept role-gated instead of owner-grantable?