Threshold is a small confidence-tiered human-in-the-loop routing primitive. Given caller-supplied signals and a policy, the pure core returns one of three verdicts:
auto_applyroute_to_humanauto_reject
When behavior is ambiguous, the safer and more supervised outcome wins.
Verdict = auto_apply | route_to_human | auto_reject
Band = high | review | low
RiskLevel = low | medium | high | critical
Signals = {
score?: number
scores?: { [name: string]: number }
flags?: string[]
meta?: { [key: string]: unknown }
}
BandConfig = {
highMin: number
reviewMin: number
}
Rule = {
id: string
when: (signals: Signals) => boolean
force: route_to_human | auto_reject
reason: string
}
Policy = {
bands?: { [scoreName: string]: BandConfig }
combine?: (bands: { [scoreName: string]: Band }) => Verdict
rules?: Rule[]
enableAutoApply?: boolean
enableAutoReject?: boolean
}
Reason = {
code: string
message: string
source: band | rule | safety
}
Decision = {
verdict: Verdict
reasons: Reason[]
risk?: RiskLevel
bands?: { [scoreName: string]: Band }
}
- Band each score named in
policy.bands. - Combine bands into a tentative verdict. The default combine uses the lowest band as the candidate: any
lowproducesauto_reject, anyreviewproducesroute_to_human, otherwise all-high producesauto_apply. - Evaluate all rules.
- Resolve conflicts by the safest outcome: any
route_to_humanrule wins; otherwise anyauto_rejectrule produces an auto-reject candidate; otherwise keep the tentative verdict. - Apply safety gates.
auto_applysurvives only when every configured band ishighandenableAutoApplyis true.auto_rejectsurvives only whenenableAutoRejectis true. Blocked auto verdicts becomeroute_to_human. - Return the verdict with reasons for banding, fired rules, and safety gates that influenced the result.
- Rules cannot force
auto_apply. auto_applyis reachable only from an all-high band path withenableAutoApply: true.- Empty/default policies route to
route_to_human. decide()is pure and deterministic.- The queue module is additive; the core has no persistence or side effects.
The shared vectors/*.json corpus is the parity contract for language twins. Production policies use closures for Rule.when, but vectors use a small declarative fixture format so TypeScript and Swift can load the same files:
{ "flag": "needs_human" }
{ "metaNumberLessThan": { "key": "linkedCount", "value": 1 } }
{ "scoreBelow": { "name": "draft", "value": 0.8 } }Vector runners translate those fixtures into native closures before calling decide().