> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Verified Decisions API

> POST /v1/decisions — auditable approved/denied/refused outcomes with a cited proof chain, rule effects, negation-as-failure, and safe refusal.

# Verified Decisions API

The flagship neuro-symbolic-recursive surface. Instead of generating free text, it returns an
**auditable decision** — `approved | denied | refused` — with a cited proof chain, a
confidence, and a **safe refusal** when the engine cannot ground the answer well enough to be
accountable.

This is the use case where a pure LLM is unsafe: it will confidently approve actions that
violate policy. The symbolic layer gives deterministic, provenance-traced decisions relative to
the supplied facts and rules; the recursive layer handles multi-hop policy (transitive role
access, bill-of-materials/recall explosion, chained eligibility); the neural layer reads messy
natural-language requests.

Every request also runs through the canonical **Grounded Symbol System (GSS)** and a
tenant-scoped **`NSRMachine`**. Machine inference is mandatory for verified decisions — if the
seed pack is unavailable or inference does not complete, the API **fails closed with HTTP 503**.

<Note>
  This page reflects **NSR v0.7.0**. Several rule-evaluation semantics were corrected in v0.6.0
  and v0.7.0 — see [Behavior changes](#behavior-changes-in-v060-and-v070). If you wrote rules
  against earlier docs, read that section.
</Note>

## Endpoints

| Endpoint               | Method | Returns                                                                                                                      |
| ---------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `/v1/decisions`        | POST   | `approved \| denied \| refused` plus a cited proof chain, confidence, and safe refusal                                       |
| `/v1/decisions/batch`  | POST   | Per-item decisions and a batch summary. Items past the time budget are never evaluated or billed (`batch_deadline_exceeded`) |
| `/v1/decisions/recent` | GET    | Most recent decisions for the org (ephemeral analytics, newest first)                                                        |
| `/v1/decisions/stats`  | GET    | Windowed aggregates: outcome counts, billable volume, p50/p95 latency, top-cited rules                                       |

```
POST /v1/decisions
Authorization: Bearer <NSR_API_KEY>   (or X-API-Key)
```

## Request

```json theme={null}
{
  "query": "Can order A1 be refunded for customer C?",
  "action": "issue_refund",
  "mode": "safe",
  "facts": [
    { "predicate": { "name": "return_received", "args": ["A1"] }, "confidence": 1.0 }
  ],
  "rules": [
    {
      "name": "refund_if_clean",
      "effect": "permit",
      "if": [
        { "name": "return_received", "args": ["?o"] },
        { "name": "fraud_flagged", "args": ["?o", "?r"], "negated": true }
      ],
      "then": [ { "name": "may_refund", "args": ["?o"] } ]
    }
  ],
  "hydrate_org_context": true,
  "include_trace": false
}
```

| Field                 | Purpose                                                                                                                           |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `query`               | The natural-language request                                                                                                      |
| `action`              | Optional — the action being authorized                                                                                            |
| `mode`                | `fast` \| `accurate` \| `safe`. Defaults to **`safe`**, the most thorough verification, because decisions are accountable outputs |
| `facts`               | Optional request-scoped facts, each with a confidence                                                                             |
| `rules`               | Optional request-scoped rules                                                                                                     |
| `hydrate_org_context` | Pull the org knowledge base (default `true`)                                                                                      |
| `include_trace`       | Include the full proof trace and raw payload                                                                                      |

## Rule effects

A rule may declare its policy intent explicitly via `effect`, which is **authoritative over
predicate-name conventions** — better for tenant-specific or non-English vocabularies.

| Effect   | Meaning                                                                                                                       |
| -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `permit` | A satisfied rule authorizes the action.                                                                                       |
| `deny`   | A satisfied rule blocks it. **Deny wins over permit.**                                                                        |
| `review` | A satisfied rule routes the decision to human review — `refused` plus `requires_human_review`. The third first-class outcome. |

<Note>
  `effect: "review"` became a first-class declared effect in **v0.6.0**. Before that it was
  expressible only via predicate-name inference, and an inline rule declaring it would fail with
  a 422 "unknown variant". Review rules are subject-bound in the same way deny rules are.
</Note>

## Negation-as-failure

A condition may set **`negated: true`**: the rule fires only when no fact matches the atom. A
free variable in the negated atom is a wildcard, so `not fraud_flagged(?o, ?r)` fails whenever
any `fraud_flagged(?o, *)` exists.

```json theme={null}
{ "name": "fraud_flagged", "args": ["?o", "?r"], "negated": true }
```

<Warning>
  **This field is required for negation to take effect.** Before v0.6.0, request-scoped rule
  conditions carried no `negated` flag, so a negation was silently dropped and read as a
  *positive* condition — inverting the guard. A rule written as
  `may_auto_refund(?o) :- refund_requested(?o), NOT fraud_flagged(?o,?r)` would auto-refund
  exactly the fraud-flagged orders it was written to block.

  Organization rules on the batch endpoint always honored negation; **inline rules — what the
  MCP `nsr_decide` tool uses — did not.** If you authored inline rules against pre-v0.6.0 docs,
  audit them.
</Warning>

`negated` is serde-default and skipped when false, so existing payloads are unchanged.

## Outcomes

| Outcome      | When                                                                                                                                                                                                                                                                                                                                                        |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **refused**  | A fired cited rule with `effect: "review"` (routing to `requires_human_review`); the GSS safety/escalation gate; or — when no cited policy rule resolves the outcome — engine failure, ungrounded (status ≠ grounded and `proof_score` \< 0.34), or grounded-but-ambiguous. **The safe default** — NSR refuses rather than guesses.                         |
| **approved** | A fired, cited request rule with `effect: "permit"` (or, without a declared effect, a positive conclusion `may_*`, `can_*`, `authorized`, `approved`, `eligible`, `permitted`) whose ground conclusion **binds the request's subject and action**. Otherwise, a grounded result with a `ready` tool call, approved action type, or affirmative answer flag. |
| **denied**   | A fired, cited rule with `effect: "deny"` (or, without a declared effect, a negative conclusion `denied`, `blocked`, `forbidden`, …) binding the request. Deny wins over permit.                                                                                                                                                                            |

Cited symbolic policy is evaluated **after** GSS safety gates but **before** conversational
uncertainty and execution-runtime state. This matters when policy authorizes an action but
answer composition is ambiguous or live runtime state disagrees.

## Response

```json theme={null}
{
  "decision_id": "dec_…",
  "decision": "approved",
  "confidence": 0.85,
  "rationale": "…",
  "plain_explanation": "Approved to issue a refund for order 9412: the return for order 9412 was received and order 9412 passed inspection.",
  "proof": {
    "cited_rules": ["may_refund"],
    "symbolic_steps": 2,
    "neural_steps": 0,
    "planner_steps": 1,
    "proof_score": 0.9,
    "proof_status": "grounded"
  },
  "grounding": { "status": "grounded", "grounded_entity_count": 3, "cited_rule_count": 1 },
  "gss_machine": {
    "authority": "nsr_machine_gss",
    "seed_source": "embedded",
    "seed_grounded": true,
    "categories": ["returns_exchanges"],
    "domains": ["order"]
  }
}
```

`cited_rules` is the audit trail, reduced to the backward dependency path. When the request
carried inline policy, the proof also carries a SHA-256 over the canonicalized request-scoped
facts and rules, pinning the decision to the exact policy inputs it was evaluated against.

## Behavior changes in v0.6.0 and v0.7.0

Several of these were **fail-open correctness bugs**. If you built against earlier
documentation, the following now behave differently — in every case, more conservatively.

### Confidence is capped by the weakest supporting fact (v0.7.0)

Decision confidence was `1.0 - grounding_uncertainty`, decoupled from the proof and
non-monotonic — a 0.4-confidence fact could yield *higher* confidence than a 1.0 one. An
approval is now capped by its weakest supporting fact:

```
confidence = min(grounding, weakest request-fact confidence)
```

This is fail-safe (it only lowers values), monotonic, and a no-op when facts are certain.

### Subject and action binding (v0.6.0)

A rule fires for whatever the fact base satisfies, not for what the question asks. Given
`may_authorize(?p,?act) :- has_authority(?p,?act)` and the fact
`has_authority(ceo_kim, approve_wire)`, the engine proves `may_authorize(ceo_kim, …)`.

The gate previously counted any cited request-scoped effect wholesale, so a wire could be
authorized for `stranger_sven` off the CEO's authority, or `delete_account` authorized off
`refund` authority. A single conclusion-head check now covers **all** effects: the cited rule's
effect only applies when its ground conclusion binds the request's own subject and action.

Two related binding fixes:

* **Numeric builtins** — the binding gate replays a cited rule's body to reconstruct which
  subject it fired for, but the replay matcher did not evaluate comparison builtins (`leq`,
  `geq`, `lt`, …). It returned "can't tell", so no mismatch was detected. A rule
  `may_refund(?o) :- order_amount(?o,?v), leq(?v, 500)` could approve a refund for an order
  whose amount did *not* qualify. The matcher now computes comparison builtins under the
  binding, exactly as the solver does.
* **Single-character subject tokens** — the subject-binding token match dropped tokens shorter
  than 2 characters, so `ord-9` and `ord-4` (sharing `ord`, differing only in the 1-char
  suffix) were treated as the same subject. A rule that fired for `ord-9` could bind a decision
  about `ord-4`. The match now requires **every** alphanumeric token, single characters
  included. Phrasing variance (`order A1` vs `order #A1`) still grounds.

### Abduction no longer asks for un-suppliable builtins (v0.7.0)

A failed comparison builtin such as `leq(850,500)` was reported in `missing_facts` with a
`resolves_with`, telling an agent to "supply `leq(850,500)`" — impossible. Comparison builtins
are now excluded; the actionable ask is the base predicate that binds the value, e.g.
`amount`.

## MCP server

The NSR MCP server gained a per-request timeout (`NSR_TIMEOUT_MS`) so an agent never hangs on a
slow endpoint, plus client-side input validation that names the exact malformed rule or field
instead of surfacing a cryptic 422 deserialize error.

## Verification

NSR ships a **verifier-as-oracle** evaluation apparatus: a 1,260-case auto-labeled
decision-soundness corpus, a deterministic generator, and a runner that checks any live
`/v1/decisions` deployment against the ground-truth labels — plus in-process property-based and
generative multi-invariant soundness tests.

## Related

* [Neuro-Symbolic Architecture](/stateset-nsr) — the conceptual model
* [NSR-L](/stateset-nsr-l) — the rule language
