Metadata-Version: 2.4
Name: ziffer
Version: 0.1.0
Summary: Ziffer decision API client and ACP receipt verifier (wraps the reference implementation at the engine pin)
License: Proprietary
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=42
Requires-Dist: dilithium-py>=1.0

# ziffer — Python SDK (ACP-197)

Client for the Ziffer decision API plus the customer-side receipt verifier.

The verifier **wraps the ACP reference implementation** — it calls the
reference's own functions (`canon`, `h`, `sig_ok`, `Timestamp.parse`,
`Bundle.suite_ok`, the WE-4/L-17 nonce rules) and never reimplements them.
Refusal names are the reference's clause ids, identically. The committed
source here is the wrapper only; `build.sh` copies the needed
`reference/src/*.py` modules from an engine checkout **at the pin** into
`src/ziffer/_engine/` (gitignored) at build/test time. This is build-time
distribution under the public freeze, not a fork: the copy is verbatim,
header-stamped with its source rev, and regenerated on every build.

```sh
./build.sh                                   # copies the engine at the pin
PYTHONPATH=src python3 -m unittest discover -s tests -v
python3 -m build                             # sdist + wheel (engine included)
```

```python
import ziffer

client = ziffer.Client("https://api.example", api_key="zfr_...")
d = client.propose(proposal)                 # -> Decision(decision_id, status, ...)
d = client.wait(d.decision_id, timeout=30)

anchor = ziffer.TrustAnchor.from_file("receipt-identity.pub.json")  # acp-bundle pubkey output
verified = ziffer.verify(d.receipt, proposal_bytes, anchor)          # or raises RefusedError(name)
```

`verify()` runs the **stateless** §9.3 subset (AB-0, AB-6, CR-4, 9.3-1/CR-3,
9.3-2, 9.3-3, 9.3-5/L-14, WE-4/L-17). It does not — cannot, statelessly —
check the policy basis (step 4), nonce single-use (CL-2), or steps 7–10;
a `Verified` means the stateless half found nothing, not the full §9.3 verdict.
