Decision and enforcement points
All names, domains, and identifiers in these examples are fictional. At 13:30 UTC on 14 June, Omar opens photo 9002, the regatta winner from the Riverside festival, and selects Download. The photo is approved but not yet published, and it is embargoed until 14:00 UTC, when the result is announced. Omar is a picture editor on the sports desk and is working on a managed laptop, so the request satisfies almost every rule the newsroom has. The embargo is the exception, and the download has to be refused.
Separating the question from the answer
Every check in this series has had two halves, even when both fit on one line such as can(user, 'photo.download', photo). One half asks: may this subject take this action on this resource, in this context? The other half answers, by applying the newsroom's rules to what is known about the person, the photo, and the moment.
For a long time the photo service kept both halves together in each handler. The web app's download route checked the role, then the device, then compared the clock with the embargo time. The mobile app's backend had its own copy of those checks, and the public API had a third. When the Gazette introduced embargoes, someone had to change all three, and the mobile copy was missed. For a week, the same download was refused in a browser and allowed on a phone.
Separating the halves removes that problem. The code that receives a request only asks, and something else answers. The download handler describes the request:
{
"subject": { "id": "user-1187" },
"action": "photo.download",
"resource": { "type": "photo", "id": "9002" },
"context": {
"time": "2026-06-14T13:30:00Z",
"device": "laptop-0417",
"request_id": "req-9c41"
}
}
The answer comes back with a reason and the version of the policy that produced it:
{
"decision": "deny",
"reason": "embargoed",
"policy_version": 14
}
The handler no longer contains the embargo rule, or any other rule. It knows how to refuse a download and how to turn the reason embargoed into a message Omar will understand. The rules live in one policy that the web app, the mobile app, and the API all consult. Changing a rule means publishing a new policy version rather than editing handlers, and all three give the new answer from the moment it is published.
Every question has the same four parts: the subject, the action, the resource, and the context, which the rules read as environment attributes. The subject here is a person, but it does not have to be. A nightly job, another service, or an automated agent working for an editor can ask the same question about itself. How such subjects are identified, and how much authority they should hold, belongs to workload and agent identity, a topic of its own.
Four parts of a decision
Once asking and answering are separate, the parts that produce a decision have names that are used across the field, often as abbreviations:
- The enforcement point (PEP) sits in the path of the request. It asks the question and carries out the answer. For Omar's download, it is the photo API.
- The decision point (PDP) evaluates the policy for one question and returns a decision. Here, it is a decision service that the photo API calls.
- Information points (PIPs) supply the attributes and relationships the policy needs. The photo library knows each photo's status and holds the role assignments that give each person's roles, the rights database knows its embargo time, device management knows which laptops are managed, and the HR system knows each person's desk.
- The administration point (PAP) is where policy is written, reviewed, and published. The decision point evaluates whichever version was published last.
Omar's download passes through all four:
Policy version 14 was published from the administration point before the day began. When Omar selects Download, the browser sends GET /photos/9002/download to the photo API. The API decides nothing itself. It sends the decision service the question shown earlier and waits. That question names Omar, the photo and the laptop without describing them, so the decision service asks the information points. The photo library reports that Omar holds Picture editor in the Sports library, where the photo lives, and that photo 9002 is approved but not yet published, the rights database reports an embargo until 14:00 UTC, and device management reports that laptop-0417 is managed.
With those values, the decision service evaluates the rules. The rule that lets picture editors download unpublished photos on managed devices matches, and so does the rule that forbids downloads before an embargo ends. The forbid wins, and the answer is a denial with the reason embargoed. The photo API enforces it with a 403. Omar can already see photo 9002, so the refusal reveals nothing Omar does not know.
Embedded, central, or alongside
The diagram draws the decision point as a separate service, but that is only one place to put it. A decision library can run inside each service's own process. A central decision service can answer for every service over the network, as in the diagram. Or a decision process can run alongside each service, on the same machine or in the same group of containers, answering over a local connection while its policy and data are published centrally and pushed to every copy.
| Placement | Cost of each decision | If something fails | Keeping policy consistent |
|---|---|---|---|
| Library inside each service | A function call | Keeps answering from the policy and data it already holds | Each service loads new versions and keeps its own data current |
| Central decision service | A network round trip | No decisions while it is down or unreachable | One version and one view of the data for every service |
| Process alongside each service | A local call | Copies keep answering from the last version they received | Published once and pushed to every copy, which may briefly disagree |
A library's speed comes with the hardest consistency problem. When version 15 is published, the photo API, the mobile backend, and a nightly export job each have to load it, with fresh copies of the data it reads, and they will not all do so at the same moment. For a while, the same request can get different answers depending on which service receives it. A central service avoids that and can record every decision in one place, but its round trip is paid on every question, which adds up on a page that asks dozens. It also sits on the critical path of everything it protects, so it has to be as reliable as the most important service that depends on it. A process alongside each service is a common compromise: local speed and one place to publish, with many copies whose versions someone has to watch.
The placement also shapes how attributes reach the decision. A central service can fetch them from the information points, as in the diagram, at the cost of extra calls. A library or a process alongside the service usually works from data pushed to it in advance, such as role assignments, which is fast but only as fresh as the last update. In any arrangement, the enforcement point can send some attributes with the question, and that is reasonable for facts it is the authority on. The photo API serves the photo library, which is responsible for each photo's status, so sending photo 9002's status saves a lookup without trusting anyone new. Letting each caller supply the subject's roles is different: every decision would then be only as trustworthy as the least careful service that asks, a concern the lesson on where attributes come from raised about any source.
When the decision point cannot answer
Sooner or later the photo API will ask and hear nothing back. The decision service is restarting, a network link has failed, or the rights database has stopped responding. The download handler still has to do something with Omar's request.
It has to refuse. A missing answer does not establish that the download is allowed, any more than a request that matches no rule does, so the enforcement point fails closed. The tempting alternative is a switch that allows requests while the decision service is down, usually added after an outage to keep the newsroom working. It turns every outage into a period with no authorization at all, and it gives anyone who can make the decision service slow, for example by flooding it with requests, a way to switch authorization off.
Failing closed only works if failure is noticed quickly. Without a deadline of its own, the photo API might wait on a stalled decision service for as long as its HTTP library allows, which may be a minute or forever, while its workers fill up with waiting requests. The enforcement point should set a short, explicit timeout on every question, allow at most one quick retry, and refuse the request when the deadline passes.
Caching decisions means asking less often, and it is safe within two limits. The first is time. A cached permit keeps working after the rule or the facts behind it change, the same lag that the lesson on administering roles safely described for removed access, so cached answers should expire within seconds or minutes. A decision can also carry its own end: the 13:30 denial for photo 9002 is true only until 14:00, and a cache must not hand it out at 14:01. The second limit is the key, which must include everything that affected the answer: the subject, the action, the resource, the context values the rules read, and the policy version. A cache keyed on the subject and the photo alone would let a permit to view photo 9002 answer a question about downloading it, or let a permit given to Omar's managed laptop answer a request from Omar's personal one.
Finally, Omar should be told the truth. A message saying the download is not permitted would be wrong, and might send Omar to Theo, the library administrator, asking for access that would change nothing. The photo API should say that the check could not be completed and the download can be tried again shortly, typically with a 503 rather than a 403, and record the event as an unavailable decision rather than a policy denial. The newsroom can then see an outage for what it is, instead of a sudden wave of refusals.