# Uncertainty and acceptance policy

> Three-valued truth, the default thresholds, withPolicy, and how code predicates short-circuit requests.

A model answer is a probability or a distribution. A decision is yes or no. The acceptance policy is the set of thresholds that turns one into the other, and everything the policy will not decide stays `uncertain`.

## Three-valued truth

Every predicate resolves to `true`, `false`, or `"uncertain"`. Composite conditions combine these with Kleene logic rather than multiplying probabilities:

* `false and uncertain` is `false`; `true and uncertain` is `uncertain`.
* `true or uncertain` is `true`; `false or uncertain` is `uncertain`.
* `not uncertain` is `uncertain`.

The tables live in the `logic` namespace if you need them directly.

## The thresholds

`defaultPolicy` is a conservative starting point:

* Noul: accept yes when P(yes) ≥ 0.8, accept no when P(yes) ≤ 0.2. Between the two is uncertain.
* Choice: accept the chosen option when confidence ≥ 0.5.
* Score: accept a score when confidence ≥ 0.5.

Noul reports a yes-probability; Choice and Score report a distribution-derived confidence. The two are not interchangeable, so they get separate knobs. `noBelow` must be strictly less than `yesAbove`; `resolvePolicy` throws a `RangeError` otherwise.

These are application thresholds, not universal truths. Tune them against your own fixtures with [`grade`](/Jevlish/guides/testing/): lowering `yesAbove` raises coverage and risks false positives; raising it does the reverse.

## Setting them

For the shared runtime, pass `policy` to `configure`. For one expression, call `.withPolicy({...})` on `given`, `from`, or any predicate, branch, query, measurement, choice, or `ask` expression they build. Either form accepts any subset of the fields.

```ts
import { configure, defaultPolicy, given } from "jevlish";
import { ticket } from "./support.js";
import { blocked } from "./vocabulary.js";


defaultPolicy;
// { noul: { yesAbove: 0.8, noBelow: 0.2 }, choice: { minConfidence: 0.5 }, score: { minConfidence: 0.5 } }


// For the shared runtime:
configure({ policy: { noul: { yesAbove: 0.9, noBelow: 0.1 } } });


// For one expression:
const lenient = await given(ticket).withPolicy({ noul: { yesAbove: 0.7, noBelow: 0.3 } }).when(blocked);


lenient.evidence.policy.noul.yesAbove; // 0.7: the policy that was applied is part of the evidence
```

The policy that was applied is part of every judgment’s evidence, so a result can always be read against the thresholds that produced it.

## Code settles what it can before anything is sent

Because `false and x` is `false` no matter what `x` is, a false code predicate under `and` decides the whole expression locally. The Noul is never asked. `plan()` reports these as `decidedByCode`.

```ts
import { given } from "jevlish";
import type { Ticket } from "./support.js";
import { blocked } from "./vocabulary.js";


const closed: Ticket = { id: "T-9", status: "closed", subject: "Login down", body: "Nobody can log in." };


// `false AND uncertain` is false, so a false code predicate settles the whole
// expression before anything is sent.
const plan = given(closed)
  .when(blocked)
  .and((t) => t.status === "open")
  .plan();


plan.decidedByCode; // 1
plan.requestCount; // 0
```

The symmetric rule applies to `or`: a true code predicate settles `true or x` without asking `x`. The same folding happens per item in `from(...)`, so closed tickets never reach the model when `.where((t) => t.status === "open")` is in the chain.

## Thresholds apply after the cache

A cached response is the raw model answer. The policy is applied when the answer is read, so changing thresholds changes decisions without invalidating the cache. See [Runtime](/Jevlish/guides/runtime/).