Metadata-Version: 2.5
Name: zanii-certify
Version: 0.7.1
Summary: Build and verify Zanii Certify certificates for AI agents: fingerprints, signed certificates, status documents, test suites and the certify command line tool.
Project-URL: Homepage, https://zanii.agency
Author-email: Zanii <info@zanii.agency>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai-agents,ai-assurance,arabic,certification,ed25519,post-quantum,verifiable-credentials,zanii
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.11
Requires-Dist: zanii-blackbox<0.24,>=0.23.0
Requires-Dist: zanii[pq]<0.27,>=0.26.2
Description-Content-Type: text/markdown

# zanii-certify

Python SDK and `certify` CLI for Zanii Certify fingerprints, signed certificates,
status, evidence sharing, runtime assurance and scheme governance. Requires Python
3.11 or newer. Public functions are exported by the `zanii_certify` module; common
signed formats are checked against the TypeScript SDK (`@zanii/certify` on npm) using
shared vectors.

## Install

```sh
pip install zanii-certify
certify verify cert.json --online https://lab.zanii.agency
```

Certificate, status and signature checks work on every supported Python. The strict
text-normalisation policy used to grade test suites pins the Unicode version and
currently supports Unicode 16 and 17 (Python 3.14 or newer); on an older Python it
reports the policy as unsupported instead of grading with different Unicode tables.

Paths in backticks below (`spec/...`, `conformance/...`) are files of the Zanii Certify source repository, which is private; the specification documents are available from Zanii on request (info@zanii.agency).

## Use this checkout

From the repository root, install the locked workspace and inspect the current CLI:

```sh
uv sync --frozen
uv run certify --help-v2
```

Save this as `example.py` at the repository root and run `uv run python example.py`.
It needs no server, network access or signing key:

```python
from zanii_certify import (
    build_fingerprint,
    check_fingerprint,
    prompt_sha256,
    runtime_request_hash,
    sha256_bytes,
)

fingerprint = build_fingerprint(
    model="example-model@1",
    prompt=prompt_sha256("Answer using the approved policy."),
    tools=None,
    policy=None,
    knowledge=None,
)
assert check_fingerprint(fingerprint)
print(fingerprint["id"])

# Commit the exact request body bytes; this hash alone grants no permission.
body = '{"amount_minor":100,"currency":"USD"}'
print(runtime_request_hash({
    "method": "POST",
    "url": "https://bank.example/payments",
    "body_sha256": sha256_bytes(body),
}))
```

This produces the same two hashes as the
TypeScript example (`packages/certify/README.md`). Fingerprint inputs contain
hashes or `None`, rather than raw prompts/documents. Keep the original bytes:
changing whitespace can change a commitment.

## Verification policy

`verify_certificate(certificate, **options)` is synchronous and returns a dictionary
with `ok` and `reasons`. Optional checks run when their inputs/policies are supplied;
an `ok` result alone does not mean every relying-party requirement was checked.

- Supply fresh signed `status` and `revocation_list`, the expected `fingerprint`,
  and `now` when a reproducible verification time is required.
- Supply an independently approved `issuer_policy` and corresponding `issuer_doc`.
  Downloaded metadata cannot nominate trusted keys. Key/PQ changes require local
  review; `allow_no_pq` cannot override the pinned policy.
- Supply `pq`/`status_pq` when required. For Level 3, supply the audit plus
  `trusted_bodies` or an independently rooted `body_register`. Add witness,
  licence and release policies where required. Strict withdrawal checking uses
  the withdrawal log and `releases["rejectWithdrawn"] = True`; signed JSON policy
  field names retain their wire spelling, even in Python.

See the full verify_certificate signature (`python/src/zanii_certify/certificate.py`),
policy tests (`python/tests/test_verification_policy.py`) and
certificate contract (`spec/certificate.md`). Legacy optional behavior remains
for compatibility; choose your relying-party policy explicitly. Offline evidence
cannot reveal an undisclosed newer checkpoint or revocation.

The CLI is offline by default. `--help-v2` lists current strict-policy, audit,
witness and release options. `--online` enables bounded retrieval, including
required PQ material. Accept only exit code 0 when every CLI check is required;
exit code 2 means not fully checked. See the CLI contract (`spec/cli.md`).
Python issuer retrieval uses at most four daemon-worker slots; a timed-out
standard-library request may occupy its slot until it finishes.

## Contracts and limits

| Area | Contract |
| --- | --- |
| Fingerprints and certificates | Fingerprint (`spec/fingerprint.md`), certificate (`spec/certificate.md`) |
| Status and freshness | Status v1/v2 (`spec/status.md`), witnesses (`spec/witness.md`), Bitstring Status List (`spec/bitstring-status.md`), assurance withdrawal (`spec/assurance-status.md`) |
| Runtime releases, mandates, leases and knowledge proofs | Runtime assurance (`spec/runtime.md`) |
| Scheme releases, exact text and transitions | Scheme (`spec/scheme.md`), immutable text (`spec/scheme-text.md`), checkpointed log (`spec/scheme-log.md`) |
| Redacted evidence and inclusion proofs | Evidence sharing (`spec/evidence-share.md`) |

Format-specific byte/count limits bound builders and verifiers. Preserve trusted
freshness watermarks independently when the contract requires them. An inclusion
proof establishes commitment, not content truth, ACL permission or quality.
Node-only Nitro verification, runtime reporter and reference HTTP gateway are
not exported by this Python package.

## Beacon reporting

`Beacon.start()` sends immediately, then waits the configured interval after each attempt.
Intervals must be between 1,000 and 2,147,483,647 milliseconds. One underlying operation is
allowed per client; concurrent `send()` calls return a busy failure without queuing.
`send()` waits at most ten seconds for fingerprint collection, signing and transport.
The default transport refuses redirects and credentialed URLs and caps replies at 32KiB.

`stop()` disables automatic reporting and wakes the active sender. Manual `send()` remains
available. Python cannot forcibly terminate an injected callback, so the client keeps its
operation slot until that callback returns and prevents subsequent network steps after
cancellation. It never replaces a still-running operation. Result observer exceptions do
not interrupt reporting. Custom fetch implementations own their transport response limits
and must not follow redirects.

`Beacon(..., transport_key=os.environ.get("CERTIFY_TRANSPORT_KEY"))` optionally sends
an agent-bound `ctk_` admission credential in the authority's Authorization header.
It is absent from the signed report and its hash. Management keys are refused, and
HTTPS is required except literal loopback/localhost development endpoints. Anonymous
signed reports remain supported; this credential does not replace signature or replay
verification. Response/callback echoes redact the supplied key. Keep custom transports
private and refuse redirects.

Python runtime APIs build and verify signed documents; there is no Python runtime
reporter, gateway or HTTP lease client to configure with this transport option.

## Check changes

Run from the repository root:

```sh
uv run pytest python/tests
uv run pytest python/tests/test_verification_policy.py
uv run ruff check python
```

Shared vectors (`spec/vectors`) and conformance tests (`conformance`) verify
the common wire formats and preserve old signed bytes when adding new versions.
Local SDK tests do not qualify deployment security or capacity; see the
readiness report (`docs/readiness-report.md`).
