Policy Obligations
Policy decision and obligation wire contract used by the PDP
This page is the source of truth for the policy decision schema consumed by nexus and, optionally, Aegis for classification ceiling checks.
It documents the wire contract of the vortex-policy-core crate.
A policy decision point (PDP) answers: given this subject, action, resource, and context, should the action be allowed, and with what obligations?
The PDP does not replace authentication, service scopes, or tenant-boundary checks. Those coarse checks prove who the caller is and whether the caller may reach a service surface at all. The PDP evaluates contextual authorization and data-handling rules after identity has been established.
PolicyQuery
Section titled “PolicyQuery”| Field | Type | Required | Notes |
|---|---|---|---|
query_id |
u128 ULID string |
yes | For correlation in audit. |
tenant_id |
string |
yes | |
action |
string |
yes | e.g. gateway.request, model.load, config.update. |
subject |
PolicySubject |
yes | The caller: user, API key, service identity. |
resource |
PolicyResource |
yes | What is being acted on. |
context |
map<string, cbor> |
yes | Free-form request-time attributes (classification, model id, token estimate, etc.). For gateway.request queries this includes classification_tiers: the org’s ordered classification vocabulary (list of label strings, low to high) that the PDP uses to rank labels. |
PolicySubject
Section titled “PolicySubject”| Field | Type | Notes |
|---|---|---|
kind |
string |
user, api_key, service. |
id |
string |
Stable identifier. |
attrs |
map<string, cbor> |
Role, clearance, groups. max_classification lives here. |
PolicyResource
Section titled “PolicyResource”| Field | Type | Notes |
|---|---|---|
kind |
string |
request, model, provider_key, audit_event, endpoint, export. |
id |
string? |
|
attrs |
map<string, cbor> |
classification lives here for request resources. |
For gateway.request queries the resource describes the gateway’s routed endpoint set.
kind is endpoint, id is the primary (first) candidate’s endpoint key, and attrs carry classification, the primary candidate’s provider and model, and candidate_endpoints: the ordered list of every endpoint the gateway may dispatch the request to, fallbacks included, each entry a map with provider and model keys.
The gateway evaluates policy once per request over this full candidate set, before any provider credential is fetched; a deny prevents dispatch to every candidate.
The policy.decision audit event echoes candidate_endpoints when present.
PolicyDecision
Section titled “PolicyDecision”| Field | Type | Required | Notes |
|---|---|---|---|
query_id |
u128 ULID string |
yes | Echoes PolicyQuery.query_id. |
effect |
Effect |
yes | See below. |
obligations |
[Obligation] |
yes | May be empty. |
reason_code |
string? |
no | Enum-like stable key, e.g. classification_ceiling_exceeded. |
policy_id |
string? |
no | The rule that produced this decision. Thin PDP uses thin.classification_ceiling; full policy services use stable product policy ids. |
evaluated_at_unix_nanos |
u64 |
yes |
Effect
Section titled “Effect”allowdeny
There is no permit_with_condition effect. Conditions are expressed as obligations that the caller must apply; if the caller cannot apply them, the caller denies itself.
Obligation
Section titled “Obligation”| Field | Type | Notes |
|---|---|---|
kind |
string |
One of the kinds below. |
params |
map<string, cbor> |
Kind-specific. |
The caller applies obligations before performing the action.
If an obligation cannot be applied, the caller treats the decision as deny.
Supported obligation kinds:
| Kind | Params | Applied by | Notes |
|---|---|---|---|
redact |
{ fields: [string], strategy: "hash" | "asterisk" | "drop" } |
Gateway (pre-provider), Ingest (pre-write) | Redacts named fields. |
classify |
{ tier: string } |
Gateway | Sets or raises the request’s classification. Never lowers. |
block |
{} |
Any caller | Stop processing and return an error. Equivalent to effect: deny but routed as a policy-authored stop. |
audit_tag |
{ tags: [string] } |
Any caller | Adds tags to the emitted audit event. |
rate_limit |
{ tokens_per_minute: u32 } |
Gateway | Applies an additional rate limit for this request. |
route_constraint |
{ allowed_providers: [string] } |
Gateway | Restricts routing to the named providers. |
New obligation kinds are a non-breaking change only if consumers that do not recognize a kind treat the decision as deny by default (the “unknown obligation means deny” rule).
Classification evaluation
Section titled “Classification evaluation”Classification vocabularies are tenant configuration, carried on every query rather than held by the PDP.
The PDP ranks the resource classification and the subject max_classification within the classification_tiers list from the query context and fails closed:
| Condition | Decision |
|---|---|
classification_tiers missing or invalid |
deny, reason classification_vocabulary_missing |
| Request label or ceiling missing | deny, reason classification_label_missing |
| Either label not defined by the vocabulary | deny, reason classification_label_unknown |
| Request label outranks the ceiling | deny, reason classification_ceiling_exceeded |
Wire encoding
Section titled “Wire encoding”PolicyQuery and PolicyDecision are encoded with deterministic CBOR, using the same deterministic-map rules as the audit event contract.
Cross-process transport is gRPC.
Audit coupling
Section titled “Audit coupling”Every PDP evaluation MUST produce an audit event of kind policy.decision.
The event attributes include query_id, effect, action, resource_kind, resource_id, reason_code, policy_id, and a compact obligation summary.
Denied decisions are represented by effect = "deny" rather than by a separate event kind.
