Docs Product

Decisions

A decision is KIFF’s answer to “is this action allowed, right now?” — produced before the action runs. This page covers what’s evaluated, the four outcomes, and the lifecycle from proposal to recorded decision.

What this does

Your runtime proposes an action. KIFF evaluates that proposal against the entity’s current state and the control’s policy, and returns a decision. Your runtime then acts on the answer: run the action on allowed, withhold it otherwise. The decision happens at the gate, not after the fact.

The decision flow

your runtime: about to run an action
        │
        ▼
   propose to KIFF  ──►  KIFF evaluates:
        │                 • entity's current state (from events)
        │                 • the control's policy + thresholds
        │                 • required parameters present?
        │                 • does the actor's identity hold authority?
        ▼
   decision returned: allowed | blocked | approval_required | invalid
        │
        ▼
your runtime acts on it:
   allowed            → run the action
   blocked / invalid  → do not run
   approval_required  → hold until a human resolves it
        │
        ▼
   one signed receipt recorded, either way

The four outcomes

  • allowed — the proposal satisfies the policy for the current state. Your runtime runs the action.
  • blocked — a governance answer: the proposal is well-formed, but the action is not permitted in this state or breaks a rule (over a threshold, wrong state, missing authority). KIFF evaluated it and said no. Your runtime does not run it.
  • approval_required — the action needs a human sign-off first. It is held; your runtime does not run it until the approval is resolved.
  • invalid — the proposal is a broken request, not a governance answer: it is malformed or misconfigured against the contract — a required parameter is missing, a parameter fails the contract’s constraints, the contract is invalid, or it has no executor. There was nothing to decide; the request itself needs fixing.

A useful invariant: anything that is not exactly allowed means the action should not run. When KIFF runs in enforce mode through the guard, that withholding is the default behavior, and an error fails closed (withheld) rather than silently allowing. See How the guard works for the fail-safe posture.

blocked vs invalid — a decision vs a broken request

The two are distinct on purpose, and your integration should treat them differently:

  • blocked is a governance decision. KIFF looked at a valid request and refused it. It returns successfully (the decision is the result), counts as a governed operation, and surfaces in your evidence and alerts — it’s a real thing that happened.
  • invalid is not a governance decision. The request never got as far as one — either it was malformed/misconfigured, or the data plane couldn’t evaluate it right then. Either way it comes back as an error (not a success), is not counted as a governed operation, and does not raise a governance alert. Two sub-cases, and they call for different handling:
    • Malformed or misconfigured (missing/invalid parameter, invalid contract, no executor) → a client/config error (an HTTP 4xx) that is yours to fix — the same way a bad request to any API is the caller’s to correct, not a policy event.
    • Transient data-plane error (the runtime, state store, or action registry was briefly unavailable) → returned as an HTTP 5xx you should retry, not fix; nothing was wrong with your request.

So a missing or invalid parameter is invalid (fix your request), not blocked (a policy said no). Branch on this: on blocked, respect the decision; on invalid, fix a 4xx or retry a 5xx.

What data is evaluated

A decision is made against:

  • the entity’s state at the moment of the proposal, derived from the events KIFF has recorded — not a value the caller asserts;
  • the control’s policy — permitted states, required parameters, thresholds, approval rules — which you defined;
  • the actor’s identity, resolved server-side. The roles a request carries are descriptive; authority is resolved from policy keyed to an authenticated identity, never from what the caller claims about itself.

The proposal / decision lifecycle

  1. Proposal — your runtime submits the action it intends to take, with the entity and parameters.
  2. Evaluation — KIFF reads the current state and applies the policy.
  3. Decision — one of the four outcomes is returned, synchronously, before execution.
  4. Hold (if approval_required) — the proposal waits for a human resolution rather than running.
  5. Record — exactly one signed receipt captures what was decided and on what basis. See Receipts & proof packs.

Reading an entity’s lifecycle

The proposals, validations, approvals, and executions KIFF has recorded for a single entity can be read back as one chronological, read-only view — the entity’s governed history in order, assembled from records KIFF already keeps. It’s a projection, not a new decision path: it changes nothing and simply surfaces what happened. Like every read surface it is scoped to your tenant — you only ever see your own entities — and a valid entity with no history yet returns an empty lifecycle rather than an error.

Example shape

The exact request/response contract for the decide endpoint and the guard’s enforce mode lives with the runnable code in the public guard repo, kiff/kiff-guard, and on each framework page. Conceptually, your runtime sends the action and entity, and receives a decision with an outcome and a trace reference for the receipt:

proposal:   action=ISSUE_REFUND  entity=order/ord_4821  amount_cents=2500
decision:   outcome=blocked  reason=state_not_allowed  (order already FULLY_REFUNDED)
            → one receipt recorded; your runtime does not issue the refund

A blocked decision in the dashboard: the REFUND_ORDER action on an order that was not in a refundable state, with the reason state_not_allowed.

A blocked decision from a clean demo tenant — the action, the entity state at decision time, and the reason (state_not_allowed).

What KIFF does not do here

KIFF returns the decision. It does not execute the action on allowedyour runtime does — and it does not undo anything downstream. The decision proves what was authorized before execution; it does not prove what your system did afterward. That distinction is the heart of Receipts & proof packs.