Skip to main content

The trust kernel: one signed, offline-verifiable receipt envelope (avow) with a scoring face (assay) and an effect-gate face (writ).

Project description

Avow

The trust kernel: one signed receipt, three Legos.

Avow turns a claim into a receipt you can check — a small, signed record that proves exactly what was claimed and fails loudly if a single byte is changed. It ships as one installable distribution (avow) that exposes three import packages you can pick and choose:

Package It is… Depends on Install
avow the shared envelope — sign, hash, verify a receipt pydantic, pynacl, rfc8785 pip install avow
assay the scoring face — an honest number, wrapped in a receipt avow + scikit-learn/scipy/numpy pip install 'avow[assay]'
writ the effect face — a policy-gated action, sealed as a receipt avow only pip install avow

The command-line tool lives behind pip install 'avow[cli]'.

One rule you can hold in your head: the envelope signs the canonical bytes of a frozen subject and never looks inside. So the exact same sign/verify code carries a score for the scoring face and an action for the effect face. One envelope, many subjects. The dependency arrows only ever point into avowassay → avow and writ → avow; avow imports neither, which is why installing the envelope alone never pulls the heavy science stack.

Why it exists

A raw claim — f1 = 0.83, or "I deleted account 7" — is a statement with no receipt. You cannot tell whether the score ran on 12 examples or 12,000, or whether the action was actually allowed. Avow wraps the claim in a signed receipt carrying the inputs' content hash and an Ed25519 signature. Anyone holding the receipt can re-check it offline — and verification fails if anything was tampered.

The trust boundary, stated honestly. A receipt proves payload integrity under some signer. It does not, on its own, prove authenticity — the signer's public key rides in the receipt outside the signed bytes, so an attacker can re-sign a forged payload with their own key. Authenticity therefore requires pinning the signer's public key out-of-band: you pass the key you already trust (the .pub from keygen) to verify, and Avow rejects any receipt not signed by exactly that key. Never trust the key embedded in the receipt.

Quickstart (copy-paste)

# dev setup (this repo installs all faces + tooling):
uv sync --all-extras
uv run poe gate                          # ruff · ruff-format · mypy --strict · xenon A · pytest (100%)
uv run python demo/run_demo.py           # scoring face: 6 honesty acceptance cases
uv run python demo/unification_demo.py   # ONE envelope + ONE verifier, BOTH faces

Envelope only — sign and verify any frozen subject (base install, no science stack):

from pydantic import BaseModel, ConfigDict
from avow import generate_signing_key, public_key_hex, sign_payload, verify_signature

class Claim(BaseModel):
    model_config = ConfigDict(frozen=True, extra="forbid")
    what: str
    value: float

key = generate_signing_key()
pinned = public_key_hex(key)                 # pin this out-of-band
receipt = sign_payload(Claim(what="f1", value=0.83), key)
verify_signature(receipt, expected_public_key=pinned)   # raises on any tamper

Scoring face (pip install 'avow[assay]') — an honest number in a receipt:

from avow import generate_signing_key, public_key_hex
from assay import score, verify
from assay.models import ScoreRequest
from assay.settings import AssaySettings

key = generate_signing_key()
request = ScoreRequest(metric="binary", metric_version="1",
                       y_true=(0, 1, 0, 1), y_score=(0.2, 0.8, 0.3, 0.7))
receipt = score(request, signing_key=key, settings=AssaySettings())
print(receipt.payload.score)   # calibrated point — or None when the sample is too thin
assert verify(receipt, expected_public_key=public_key_hex(key)) is True

Effect face (writ, base install) — a policy-gated, sealed action:

from avow import generate_signing_key, public_key_hex, verify_signature
from writ import Allowlist, EffectRequest, KeyholderEffector, governed_gate

key = generate_signing_key()
def do_it(_req: EffectRequest) -> None: ...          # the privileged effect
gate = governed_gate(Allowlist(frozenset({"read"})),
                     KeyholderEffector(effect=do_it, signing_key=key))
receipt = gate(EffectRequest(action="read", target="ledger-7", args_digest="sha256:abc"))
verify_signature(receipt, expected_public_key=public_key_hex(key))
print(receipt.payload.decision)                       # "allow" — or "deny", still sealed

CLI (pip install 'avow[cli]'):

uv run assay keygen --out signing.key                 # also writes signing.key.pub
echo '{"metric":"binary","metric_version":"1","y_true":[0,1,0,1],"y_score":[0.2,0.8,0.3,0.7]}' > req.json
uv run assay score --request req.json --key signing.key --out receipt.json --ledger ledger.jsonl
uv run assay verify --receipt receipt.json --public-key signing.key.pub   # -> OK: receipt verified

Under the hood (for developers)

avow — the envelope. sign_payload / verify_signature / payload_digest operate only on the canonical JSON of a frozen SignedReceipt[SubjectT] subject. Canonicalization is RFC 8785 (JCS) — a byte-stable JSON form — hashed with SHA-256 and signed with Ed25519 (PyNaCl). Payloads carry no timestamp, so identical inputs yield an identical, reproducible, offline-verifiable receipt. avow.ledger is an append-only, content-addressed JSONL log with a fail-closed integrity check, generic over the subject. Coded failures live in avow.errors (avow.* codes under AvowError).

assay — the scoring face. A thin trust + honesty + composition layer over reused libraries (it computes no metric math itself): scikit-learn for precision/recall/F1, PR-AUC, ROC-AUC and Brier; scipy.stats.bootstrap for percentile intervals with a sample-size floor (below it, Assay abstains rather than invent a point estimate); population-weighted ECE for calibration; a positive-weighted composite with a propagated interval. assay.receipt defines the score subjects; the envelope signs them. Its errors are assay.* under AssayError. Import assay without the [assay] extra and you get a coded ScoringExtraMissing, never a raw traceback.

writ — the effect face. writ.gate(request, policy, effector) evaluates a typed policy: on deny it seals a signed denial receipt and never runs the effect; on allow the effector runs the effect, then seals a signed effect receipt — both verifiable through the shared envelope. The v0 policy decider is a Python predicate (Allowlist); OPA/Rego is the v1 decider.

Un-bypassable seam — stated honestly. The effect credential and the privileged effect live only inside the effector, which governed_gate captures in the single closure the agent receives; the effector itself is never handed over, so the only path to the effect is through the guard. This is the v0 capability-holding approximation: the credential is still in-process, so same-process reflection could reach it. TRUE un-bypassable enforcement (a separate-process broker or a WASM guest, where the agent's address space cannot reach the credential) is the v1 hardening. We claim no more.

Signing-key custody. assay keygen (and avow.keys) write a 32-byte Ed25519 seed to a 0600 file and the public key to a companion .pub. Keys are never logged and never committed (*.key is gitignored). The public key also travels inside each receipt for convenience, but that embedded copy is not the trust anchor — a verifier pins the out-of-band key and passes it to verify.

Cross-language byte identity. testdata/vectors/ holds golden vectors generated by tests/gen_vectors.py (canonical bytes + hashes, and receipts signed with a fixed non-secret test seed). The Python suite replays them in tests/test_vectors.py; a future TypeScript @edgeproc/avow replays the same files byte-for-byte, so any RFC 8785 number-serialization divergence fails in CI, not in production.

See docs/ARCHITECTURE.md for the data-flow diagram, the import edges, and the native-vs-browser story.

Status

v0 shipped — deterministic (no LLM). Full pyramid green under uv run poe gate (ruff, ruff-format, mypy --strict, xenon A, 100% coverage). CI mirrors the gate.

License

MIT © Harish Seshadri

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

avow-0.1.0.tar.gz (41.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

avow-0.1.0-py3-none-any.whl (27.9 kB view details)

Uploaded Python 3

File details

Details for the file avow-0.1.0.tar.gz.

File metadata

  • Download URL: avow-0.1.0.tar.gz
  • Upload date:
  • Size: 41.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.4

File hashes

Hashes for avow-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f5deea68504f0d0e7691cd122af06a33cbbb04de28b54729e1c65f41cf4557aa
MD5 dca23c277bd93b8ab832d201af1b6df7
BLAKE2b-256 fff01dc7762ab48e377fc00cab870a280e474ea726fcc0a9c44110b18076f744

See more details on using hashes here.

File details

Details for the file avow-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: avow-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.4

File hashes

Hashes for avow-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6876263aa344e7a9a000e1fc4cae19ed437cb2f31f1fb2559cb3012049e00964
MD5 c7b20aae595358999bf0f26ebaa2c67e
BLAKE2b-256 9ad6d19563c60b57fa9584a3c621a864019757f41516ddaff026803722fdcbb4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page