3.4 KiB
Interface: PDP (OPA)
Data/query contract for the Policy Decision Point. Implementation:
src/emcp_bus/pdp/client.py,
pdp/models.py,
config/policies/entitlement.rego,
config/policies/transaction.rego. See
AS-BUILT.md.
Two-step model
PDPClient.evaluate(client_profile, tool, arguments, customer_classification) queries, in
order, and never in parallel:
emcp.entitlement(entitlement.rego) —allowiftoolis indata.emcp.client_profiles[client_profile].allowed_tools.DENYhere is final (reason_code=NOT_IN_ENTITLEMENT) — no argument is ever evaluated.emcp.transaction(transaction.rego) — only evaluated if step 1 allowed. Restricts by argument/resource; can never add a tool absent from the entitlement. Returnsallowandrequire_approvalindependently.
Versioned data in OPA
EntitlementManager.sync_profiles_to_pdp() pushes each validated ClientProfile to
PUT /v1/data/emcp/client_profiles/<profile_name> at boot — the Rego never reads YAML directly.
Evaluation request
POST /v1/data/emcp/entitlement/allow
{"input": {"client_profile": "Sales.Write", "tool": "finance.payment.execute"}}
POST /v1/data/emcp/transaction
{"input": {
"client_profile": "Finance.Payments",
"tool": "finance.payment.execute",
"arguments": {"amount": 15000, "region": "BR-SP"},
"customer_classification": "standard"
}}
Combined response (PDPDecision)
{"outcome": "ALLOW" | "DENY" | "REQUIRE_APPROVAL", "policy_version": "a1b2c3d4e5f6", "reason_code": "..."}
Never a lone boolean — policy_version and reason_code are mandatory in every response.
reason_code |
outcome |
When |
|---|---|---|
NOT_IN_ENTITLEMENT |
DENY |
Step 1 denied. |
TRANSACTION_POLICY_DENIED |
DENY |
Step 1 allowed, step 2 denied (limit, region, classification). |
APPROVAL_THRESHOLD_EXCEEDED |
REQUIRE_APPROVAL |
allow=true and amount > restrictions.approval_threshold. |
ALLOWED |
ALLOW |
Both steps allowed, without exceeding the threshold. |
policy_version
A SHA-256 hash (12 characters) of the content of the *.rego files (except *_test.rego) in
config/policies/, computed once at Gateway boot (compute_policy_version). Propagated in
every decision and audit event — see audit-events.md. A production
deployment would replace this with a real OPA bundle revision (see
../Production-Recommendations.md); the contract (PDPDecision.policy_version as an opaque
string) would not change.
Errors
| Situation | Exception | Handling by the caller (Gateway) |
|---|---|---|
| OPA unreachable, timeout, malformed response | PDPUnavailableError |
Treated as DENY (PDP_UNAVAILABLE) — never an implicit ALLOW (fail-closed, README.md §38). |
Failure to push a ClientProfile at boot |
PDPUnavailableError |
The Gateway does not come up "healthy" — there is no degraded mode without entitlement sync. |
How a new policy takes effect
Editing config/policies/*.rego changes the computed policy_version (content hash). There is
no hot-reload in this RI — restart the Gateway (which restarts the associated OPA process, or
point it at an already-running OPA with the updated Rego via opa run --server config/policies/).