Skip to content

Policy

Defined in: src/policy.ts:31

Ordered clauses that turn answers into an outcome. Clauses are checked in order and the first active one wins; a numeric clause with exit below enter holds its outcome until the value drops below exit (hysteresis). Every method returns a new policy; O accumulates the outcomes declared so far.

A huncho builds one of these behind .when() and .else(). Use policy() directly to decide over answers you already have, as replay does.

import { policy, type Policy } from "huncho";
type Signals = { urgency: number; billing: boolean };
const route: Policy<Signals, "page" | "billing" | "triage"> = policy<Signals>("support.route")
.when((a) => a.urgency, { enter: 0.8, exit: 0.6 }, "page")
.when((a) => a.billing, "billing")
.else("triage");
route.decide({ urgency: 0.7, billing: false }); // "triage"
route.decide({ urgency: 0.7, billing: false }, "page"); // "page": held, 0.7 is above exit

A

The answers a clause reads.

O extends string = never

The union of outcomes declared so far.

decide(answers, previous?): O

Defined in: src/policy.ts:115

The outcome for these answers. Pure: pass previous, the outcome this key got last time, and hysteresis applies; omit it and every clause starts cold.

A

What the clauses read.

string

The outcome held for this key, if any.

O

PolicyError when no clause is active and there is no else.

import { policy } from "huncho";
const gate = policy<{ p: number }>("gate").when((a) => a.p, { enter: 0.8, exit: 0.6 }, "open").else("closed");
let held = gate.decide({ p: 0.9 }); // "open"
held = gate.decide({ p: 0.7 }, held); // "open"
held = gate.decide({ p: 0.5 }, held); // "closed"

else<T>(outcome): Policy<A, O | T>

Defined in: src/policy.ts:96

The outcome when no clause is active. Without one, decide throws when nothing matches.

T extends string

T

Policy<A, O | T>

import { policy } from "huncho";
const gate = policy<{ p: number }>("gate").when((a) => a.p, { enter: 0.8 }, "open").else("closed");
gate.decide({ p: 0.1 }); // "closed"

when<T>(test, outcome, options?): Policy<A, O | T>

Defined in: src/policy.ts:52

A boolean clause: active when test is true. With exit, an outcome this clause produced last time is held while exit stays true, even if test has gone false.

T extends string

(answers) => boolean

Reads the answers; true enters the outcome.

T

What decide returns while this clause is active.

exit keeps a held outcome; omit it and the hold ends as soon as test is false.

(answers) => boolean

Policy<A, O | T>

import { policy } from "huncho";
const gate = policy<{ p: number }>("gate")
.when((a) => a.p >= 0.8, "open", { exit: (a) => a.p >= 0.6 })
.else("closed");
gate.decide({ p: 0.7 }); // "closed"
gate.decide({ p: 0.7 }, "open"); // "open": held, exit still true

when<T>(select, thresholds, outcome): Policy<A, O | T>

Defined in: src/policy.ts:79

A numeric clause: active when select is at least enter, and held while it is at least exit for a key that produced this outcome last time. exit defaults to enter, which is no hysteresis.

T extends string

(answers) => number

Reads a number from the answers, usually a probability.

enter and exit must be finite with exit at most enter.

number

number

T

What decide returns while this clause is active.

Policy<A, O | T>

ConfigError when a threshold is not finite or exit is above enter.

import { policy } from "huncho";
const gate = policy<{ p: number }>("gate")
.when((a) => a.p, { enter: 0.8, exit: 0.6 }, "open")
.else("closed");
gate.decide({ p: 0.85 }); // "open"
gate.decide({ p: 0.7 }, "open"); // "open": held
gate.decide({ p: 0.5 }, "open"); // "closed": below exit

with(overrides): Policy<A, O>

Defined in: src/policy.ts:133

A copy with different thresholds on numeric clauses, keyed by outcome. Boolean clauses and the else are unchanged. Override enter alone on a clause without hysteresis and exit follows it.

{ readonly [K in string]?: { enter?: number; exit?: number } }

Policy<A, O>

ConfigError when a resulting threshold is not finite or exit is above enter.

import { policy } from "huncho";
const gate = policy<{ p: number }>("gate").when((a) => a.p, { enter: 0.8, exit: 0.6 }, "open").else("closed");
const stricter = gate.with({ open: { enter: 0.9, exit: 0.7 } });
gate.decide({ p: 0.85 }); // "open"
stricter.decide({ p: 0.85 }); // "closed"