Metadata-Version: 2.4
Name: curbi-decisions
Version: 0.1.0
Summary: Deterministic policy checks for decision-model answers (TypeSafe Jev, Fastino GLiDE, OpenAI Decisions) before an agent acts
Author: Curbi, Inc.
License-Expression: MIT
Project-URL: Homepage, https://curbi.io
Project-URL: Repository, https://github.com/Curbi-Inc/curbi-check
Project-URL: Documentation, https://github.com/Curbi-Inc/curbi-check/tree/main/adapters/decisions
Project-URL: Issues, https://github.com/Curbi-Inc/curbi-check/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# curbi-decisions

Python port of Curbi's decision-model adapter. Decision models (TypeSafe Jev, Fastino GLiDE, OpenAI Decisions) return typed answers with probabilities. This package checks the answer against your policy before your agent acts on it, and builds a receipt. Everything is arithmetic and comparison on what your host already received: no model calls, no vendor keys, no network, no dependencies.

```python
import curbi_decisions as D
policy = D.compile_decision_policy({"decisions": {"refund_eligible": {
    "allowed": ["eligible", "ineligible", "unclear"], "act_at": 0.92, "min_margin": 0.5,
    "pin": [{"provider": "fastino", "model": "fastino/GLiDE"}],
    "calibration": {"dataset": "refund-labels-2026-10", "accuracy_at_threshold": 0.97, "measured_at": "2026-10-30T00:00:00Z"},
    "operations": {"eligible": "billing.refund"}}}})
records = D.normalize_systemone(provider="fastino", request=request, response_bytes=raw_response_bytes, received_at=now_iso)
result = D.route(policy, "refund_eligible", records, now=now_ms)          # act | escalate | hand_off | deny
receipt = D.decision_receipt(policy=policy, question_id="refund_eligible", records=records, result=result)
D.check_action(policy, "refund_eligible", result, "billing.refund")       # [] or decision:contradicted / decision:not_acted
```

- **Same behavior as the JavaScript adapter.** The tests replay 1,793 vectors generated by `@curbi/decisions`: records, routes, receipts, action findings, digests and error messages must match exactly. JavaScript semantics are kept where Python differs: key order, strict equality (`True` is not `1`), number formatting, and booleans never counting as numbers.
- **Raw bytes are authoritative.** Pass `response_bytes` (and `request_bytes`) and they are parsed strictly: valid UTF-8, no BOM, no duplicate member names, no lone surrogates. If you also pass the parsed object, it must match the bytes, or the input is malformed. Each record's `bytes_bound` says which digests cover raw bytes.
- **Limits.** Vendor probabilities are reported, not true; set thresholds on labeled data. Receipts are unsigned (`signed: false`, `provider_signed: false`). Field names follow the vendor pages recorded in the adapter's FIELDS.md. Microsoft-Decision-1 isn't modeled.
