vaxelia-ai-core
Shared compliance-reporting core for the Vaxelia AI-provider SDK wrappers
(vaxelia-ai-anthropic, and future provider wrappers). It provides a single
ComplianceReporter that ships every AI decision to your tenant's decision-log
endpoint and, when delivery fails, durably buffers the decision to an
encrypted local disk store for later retry — so an AI decision is never
silently lost.
This package is an external-integrator library: it has no internal-platform
dependencies and never instantiates a logger or telemetry client. You inject a
logger and (optionally) an HTTP transport. The only runtime dependency is
cryptography (for AES-256-GCM).
The JSON body posted to the tenant API is byte-compatible with the
TypeScript @vaxelia/ai-core SDK (the same camelCase keys), so both SDK
families target the same decision-log endpoint.
Install
pip install vaxelia-ai-core
Requires Python >= 3.9.
Configuration
Every ReporterConfig field is optional and falls back to an environment
variable, in keeping with 12-factor configuration. Explicit config always wins
over the environment.
| Config field | Environment variable | Description |
|---|---|---|
ai_system_id |
VAXELIA_AI_SYSTEM_ID |
Registered AI system identifier. Used when a decision omits ai_system_id. |
tenant_api_url |
VAXELIA_TENANT_API_URL |
Full decision-log POST endpoint URL. |
api_key |
VAXELIA_API_KEY |
Bearer token for the tenant API. |
buffer_key |
VAXELIA_BUFFER_KEY |
32-byte AES-256-GCM key, base64-encoded. Required when buffering is on. |
Additional config (no env fallback): buffer_dir, buffering_enabled,
logger, transport, sleep, max_attempts, base_delay_seconds.
Fail-closed buffer key
When buffering is enabled (the default), a valid buffer key must be
resolvable — either via config.buffer_key or VAXELIA_BUFFER_KEY. If none is
present, the constructor raises MissingBufferKeyError; if the key does not
decode to exactly 32 bytes, it raises InvalidBufferKeyError. This is
deliberate: rather than risk writing plaintext PII to disk, the reporter refuses
to start. If you do not want a disk buffer, set buffering_enabled=False — then
no key is required, but undeliverable decisions hard-fail instead of being
buffered.
Generate a key:
python -c "import os, base64; print(base64.b64encode(os.urandom(32)).decode())"
Resilience behavior
- Encryption at rest — each buffered decision is stored as a separate file
containing
nonce | ciphertext+tagand encrypted with AES-256-GCM using a fresh random 12-byte nonce per entry. Files are written with mode0o600. - Buffer directory — defaults to
<tempdir>/vaxelia-ai-buffer; override withbuffer_dir. - Exponential backoff — transient failures (network error, HTTP 5xx, HTTP
429) are retried with delay
min(base_delay_seconds * 2^(attempt-1), 30s)(± jitter) up tomax_attemptstotal attempts. Backoff only follows a transient failure and resets after a success, so a healthy endpoint drains a buffered backlog with no added inter-entry delay. - 7-day local retention — on every flush, buffered entries older than seven days are purged and a WARN is logged through the injected logger (visible, not silent).
- Hard-fail, not silent drop — if a decision can neither be delivered nor
durably buffered,
log_decisionraisesDecisionNotRecordedError. - Permanent rejection — an HTTP 4xx other than 429 is permanent: the
decision is not retried or buffered, and
log_decisionraisesPermanentRejectionError. - No secrets in errors or logs — the API key and buffer key values never appear in raised errors or logged fields.
Usage
import json
from datetime import datetime, timezone
from vaxelia_ai_core import AIDecision, ComplianceReporter, ReporterConfig
def logger(level, msg, fields):
print(json.dumps({"level": level, "msg": msg, **dict(fields)}))
reporter = ComplianceReporter(
ReporterConfig(
# omit any field to use the matching env var
tenant_api_url="https://your-tenant.example/decision-log",
api_key="...", # or VAXELIA_API_KEY
buffer_key="...", # 32-byte base64, or VAXELIA_BUFFER_KEY
logger=logger,
)
)
decision = AIDecision(
ai_system_id="ai-sys-prod-1",
model_used="claude-some-model",
input={"prompt": "Summarize the contract."},
output={"text": "The contract obligates..."},
status="completed",
decided_at=datetime.now(timezone.utc).isoformat(),
)
# Delivers to the tenant API; buffers encrypted on transient failure.
reporter.log_decision(decision)
# Periodically (e.g. on a timer or at shutdown) drain the buffer.
result = reporter.flush_buffer()
print(f"flushed {result['sent']}, {result['remaining']} still buffered")
Injecting a transport (for tests)
The default transport uses the stdlib urllib.request over HTTPS. For tests,
inject a callable (url, body: bytes, headers) -> HttpResponse that records
calls and returns scripted responses (or raises to simulate a network error).
You can also inject sleep to make backoff deterministic.
Development
python -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest
.venv/bin/python -m build # sdist + wheel into dist/
Metadata
Release files for vaxelia-ai-core 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vaxelia_ai_core-0.1.0.tar.gz | 13.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vaxelia_ai_core-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.1 kB
Release files / vaxelia_ai_core-0.1.0.tar.gz
| Download URL | vaxelia_ai_core-0.1.0.tar.gz |
|---|---|
| Size | 13.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c2a290615f28c92ce90f954320c0c24613699eebc61081c358e534cdc5db3e7
|
|
BLAKE2b-256 checksum How to use checksums |
2d91fb955d807c4995ded08fac13d81bbfdb347cb20da22558be0562b4cc77c0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / vaxelia_ai_core-0.1.0-py3-none-any.whl
| Download URL | vaxelia_ai_core-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3d1895fa01cb660224f4fb5e2844ef40bfe450db777779bb8ca1eaf2b4f7c44
|
|
BLAKE2b-256 checksum How to use checksums |
0665895587a90bd1369fe15452960839fb993ed86d70243dda7dbb167cffcb4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|