Trust infrastructure for AI agents. Checks reasoning during execution, halts when constraints are violated, generates portable cryptographic receipts proving governance was enforced.
Version: 1.5.0 | Python: 3.10, 3.11, 3.12 | See
docs/state.mdfor current test counts.
- 1. Project Structure
- 2. Module Inventory
- 2.1 Core Library
- 2.2 Constitution Engine
- 2.3 Cryptographic Layer
- 2.4 Receipt Generation & Verification
- 2.5 Coherence Checks (C1-C5)
- 2.6 Enforcement Engine
- 2.7 Evaluators & Extensions
- 2.8 Reasoning Receipts
- 2.9 Gateway / MCP Proxy
- 2.10 MCP Server
- 2.11 Storage & Analytics
- 2.12 Evidence Bundles
- 2.13 CLI
- 2.14 Integrations
- 2.15 Receipt Sinks
- 2.16 Execution Surface Interceptors
- 2.17 Utilities
- 3. Data Flow Diagrams
- 4. Data Models & Schemas
- 5. Public API Surface
- 6. Test Architecture
- 7. Integration Points
- 8. Configuration & Environment
sanna-repo/
├── src/sanna/ # Core library (54 .py files)
│ ├── __init__.py # 21 public exports, __getattr__ migration hints
│ ├── version.py # __version__ = "1.5.0" (single source of truth)
│ ├── receipt.py # C1-C5 checks, receipt assembly, dataclasses
│ ├── middleware.py # @sanna_observe decorator, receipt generation
│ ├── verify.py # Offline receipt verification, fingerprint parity
│ ├── constitution.py # Constitution parsing, signing, approval, identity
│ ├── constitution_diff.py # Structural/semantic constitution diffing
│ ├── crypto.py # Ed25519 signing/verification, keypair generation
│ ├── hashing.py # Canonical JSON (RFC 8785), deterministic hashing
│ ├── bundle.py # Evidence bundle creation/verification (7-step)
│ ├── store.py # SQLite-backed receipt persistence, WAL mode
│ ├── drift.py # Per-agent failure-rate trending, linear regression
│ ├── cli.py # All CLI entry points (except sanna-init, sanna-mcp)
│ ├── init_constitution.py # Interactive constitution generator (sanna-init)
│ ├── enforcement/ # Enforcement subsystem
│ │ ├── __init__.py # Re-exports
│ │ ├── constitution_engine.py# Invariant-to-check mapping, configure_checks()
│ │ ├── authority.py # Authority boundary evaluation
│ │ └── escalation.py # Escalation targets (log, webhook, callback)
│ ├── evaluators/ # Custom evaluator subsystem
│ │ ├── __init__.py # Evaluator registry (register, get, list, clear)
│ │ └── llm.py # LLM-as-Judge via Anthropic API (stdlib urllib)
│ ├── reasoning/ # Reasoning receipts (v0.11.0+)
│ │ ├── __init__.py # Public re-exports
│ │ ├── evaluator.py # High-level reasoning evaluation facade
│ │ ├── judge.py # Abstract BaseJudge, JudgeVerdict, JudgeResult
│ │ ├── heuristic_judge.py # Deterministic fallback judge (no API calls)
│ │ ├── judge_factory.py # Provider auto-detection, cross-provider
│ │ ├── pipeline.py # GLC check orchestration pipeline
│ │ ├── llm_client.py # AnthropicJudge, OpenAIJudge implementations
│ │ └── checks/ # Individual reasoning checks
│ │ ├── base.py # Abstract Check base class
│ │ ├── glc_001_presence.py # Justification presence
│ │ ├── glc_002_substance.py # Minimum substance
│ │ ├── glc_003_parroting.py # Anti-parroting
│ │ └── glc_005_coherence.py # LLM coherence
│ ├── gateway/ # MCP enforcement proxy (v0.10.0+)
│ │ ├── __init__.py # CLI entry point: main() → run_gateway()
│ │ ├── __main__.py # python -m sanna.gateway support
│ │ ├── server.py # SannaGateway, EscalationStore, circuit breaker
│ │ ├── mcp_client.py # DownstreamConnection: stdio MCP client
│ │ ├── config.py # YAML config, env var interpolation, SSRF validation
│ │ ├── migrate.py # One-command migration from existing MCP configs
│ │ ├── receipt_v2.py # Receipt Triad, GatewayCheckResult models
│ │ └── schema_mutation.py # _justification parameter injection
│ ├── mcp/ # MCP server (7 tools)
│ │ ├── __init__.py
│ │ ├── __main__.py # python -m sanna.mcp support
│ │ └── server.py # FastMCP server, 7 tools, stdio transport
│ ├── exporters/ # Observability integrations
│ │ ├── __init__.py
│ │ └── otel_exporter.py # OpenTelemetry bridge: receipt → span
│ ├── utils/ # Shared utilities
│ │ ├── __init__.py
│ │ ├── safe_io.py # Atomic writes, symlink protection
│ │ ├── safe_json.py # Duplicate key / NaN rejection
│ │ ├── safe_yaml.py # Duplicate key detection
│ │ ├── sanitize.py # XML escape for prompt injection prevention
│ │ └── crypto_validation.py # Ed25519 structural pre-check
│ ├── spec/ # JSON schemas (source of truth)
│ │ ├── constitution.schema.json
│ │ └── receipt.schema.json
│ └── templates/ # Constitution templates (11 YAML)
│ ├── __init__.py
│ ├── enterprise_it.yaml
│ ├── customer_facing.yaml
│ ├── general_purpose.yaml
│ ├── llm_enhanced.yaml
│ ├── financial_analyst.yaml
│ ├── healthcare_triage.yaml
│ ├── openclaw_personal.yaml
│ ├── openclaw_developer.yaml
│ ├── cowork_personal.yaml
│ ├── cowork_team.yaml
│ └── claude_code_standard.yaml
├── tests/ # Test suite (108 .py files; see docs/state.md for current count)
│ ├── conftest.py # Root fixtures, sets SANNA_CONSTITUTION_PUBLIC_KEY
│ ├── vectors/ # Deterministic test vectors (3 JSON + README)
│ ├── constitutions/ # 10 test constitution YAML files
│ ├── .test_keys/ # Pre-generated Ed25519 keypair for tests
│ ├── reasoning/ # 6 reasoning-specific test files
│ └── (87 test_*.py files) # See §6 for full listing
├── golden/receipts/ # Golden receipt test vectors (24 JSON files)
├── spec/ # Root schema copies (must sync with src/sanna/spec/)
│ ├── constitution.schema.json
│ ├── receipt.schema.json
│ └── sanna-specification-v1.5.md
├── docs/ # User/developer documentation (9 .md files)
├── examples/ # Demo scripts, constitutions, gateway configs
│ ├── *.py # 5 demo Python scripts
│ ├── constitutions/ # 10 example constitutions
│ ├── gateway/ # Reference gateway.yaml
│ └── demo_receipts/ # 4 sample receipts
├── .github/workflows/ci.yml # CI: pytest across Python 3.10-3.12
├── pyproject.toml # Package config, 17 console_scripts
├── README.md
├── CHANGELOG.md
├── CONTRIBUTING.md
└── LICENSE
Purpose: Public API surface with 21 exports and migration hints for relocated names.
| Export | Type | Description |
|---|---|---|
__version__ |
str |
Package version from version.py |
sanna_observe |
function | Decorator for runtime enforcement |
SannaResult |
class | Wrapper: agent output + receipt |
SannaHaltError |
exception | Raised when enforcement halts execution |
generate_receipt |
function | Generate receipt from trace data |
SannaReceipt |
dataclass | Receipt data model |
verify_receipt |
function | Offline receipt verification |
VerificationResult |
dataclass | Verification outcome |
ReceiptStore |
class | SQLite receipt persistence |
DriftAnalyzer |
class | Governance drift analytics |
ReceiptSink |
ABC | Pluggable receipt persistence interface |
SinkResult |
dataclass | Sink operation outcome |
FailurePolicy |
enum | LOG_AND_CONTINUE, RAISE, BUFFER_AND_RETRY |
LocalSQLiteSink |
class | SQLite-backed sink |
NullSink |
class | No-op sink (testing) |
CloudHTTPSink |
class | HTTP sink with backoff + JSONL buffer |
CompositeSink |
class | Fan-out to multiple sinks |
patch_subprocess |
function | Governance interceptor for subprocess calls |
unpatch_subprocess |
function | Remove subprocess interceptor |
patch_http |
function | Governance interceptor for HTTP calls |
unpatch_http |
function | Remove HTTP interceptor |
__getattr__ provides migration hints for names that moved to submodules in v0.12.0.
Internal deps: version, middleware, receipt, verify, store, drift
Purpose: Single source of truth for __version__ = "1.5.0".
Purpose: Constitution loading, parsing, signing, approval, and identity verification.
| Export | Type | Description |
|---|---|---|
Constitution |
dataclass | Top-level constitution model |
AgentIdentity |
dataclass | Agent name, domain, extensions, identity_claims |
IdentityClaim |
dataclass | Verifiable assertion from external provider |
IdentityVerificationResult |
dataclass | Per-claim verification outcome |
IdentityVerificationSummary |
dataclass | Aggregate verification results |
Provenance |
dataclass | Authorship, approval method, signature |
ConstitutionSignature |
dataclass | Ed25519 signature block |
Boundary |
dataclass | Scope/authorization/safety boundary |
Invariant |
dataclass | Rule with enforcement level and optional check |
HaltCondition |
dataclass | Trigger → escalation target |
AuthorityBoundaries |
dataclass | can/cannot/must_escalate lists |
EscalationRule |
dataclass | Condition + target pair |
EscalationTargetConfig |
dataclass | log/webhook/callback target |
TrustedSources |
dataclass | tier_1/tier_2/tier_3/untrusted lists |
TrustTiers |
dataclass | autonomous/requires_approval/prohibited |
ApprovalRecord |
dataclass | Single approval with Ed25519 signature |
ApprovalChain |
dataclass | Chain of approval records |
ReasoningConfig |
dataclass | Governance-level reasoning checks (v1.1) |
GLCCheckConfig |
dataclass | Base config for reasoning check |
GLCMinimumSubstanceConfig |
dataclass | Minimum-substance check config |
GLCNoParrotingConfig |
dataclass | No-parroting check config |
GLCLLMCoherenceConfig |
dataclass | LLM coherence check config |
JudgeConfig |
dataclass | Top-level judge configuration |
SannaConstitutionError |
exception | Raised on integrity check failure |
load_constitution(path, validate=True) |
function | Load and parse YAML |
save_constitution(constitution, path) |
function | Serialize to YAML |
parse_constitution(data) |
function | Dict → Constitution dataclass |
sign_constitution(constitution, private_key_path) |
function | Ed25519 sign |
approve_constitution(constitution, approver_id, ...) |
function | Add approval record |
scaffold_constitution() |
function | Blank constitution dict |
compute_constitution_hash(constitution) |
function | SHA-256 of canonical JSON |
compute_content_hash(constitution) |
function | Hash for approval binding |
validate_constitution_data(data) |
function | Returns list of error strings |
validate_against_schema(data) |
function | JSON schema validation |
constitution_to_receipt_ref(constitution) |
function | Constitution → receipt reference dict |
constitution_to_signable_dict(constitution) |
function | Canonical signing material |
constitution_to_dict(constitution) |
function | Full serialization |
verify_identity_claims(identity, provider_keys) |
function | Verify claims vs provider keys |
Key internals: _claim_to_signable_dict() (conditionally omits expires_at), _identity_dict() (sync fallback for programmatic claims), _approval_record_to_signable_dict().
Internal deps: hashing, crypto
External deps: hashlib, json, re, dataclasses, datetime, pathlib
Purpose: Structural/semantic comparison of two constitutions.
| Export | Type | Description |
|---|---|---|
DiffEntry |
dataclass | Single change: category, change_type, key, old/new |
DiffResult |
dataclass | List of entries + to_text(), to_json(), to_markdown() |
diff_constitutions(old, new) |
function | Compare two Constitution objects → DiffResult |
export_drift_report(report, fmt) |
function | Drift report → JSON or CSV string |
export_drift_report_to_file(report, path, fmt) |
function | Write drift report to file |
Diff categories: identity, boundary, invariant, authority, trust, halt, provenance, approval.
Internal deps: constitution
External deps: json, dataclasses
Purpose: Ed25519 key management, signing, and verification.
| Export | Type | Description |
|---|---|---|
generate_keypair(output_dir, signed_by, write_metadata, label) |
function | Generate Ed25519 keypair, returns (priv_path, pub_path) |
load_key_metadata(key_path) |
function | Read .meta.json sidecar |
load_private_key(path) |
function | Load Ed25519 private key from PEM |
load_public_key(path) |
function | Load Ed25519 public key from PEM |
compute_key_id(public_key) |
function | SHA-256 hex fingerprint (64 chars) |
sign_bytes(data, private_key) |
function | Sign → base64 signature |
verify_signature(data, signature_b64, public_key) |
function | Verify Ed25519 signature |
sanitize_for_signing(obj) |
function | Sanitize dict tree for canonical JSON |
sign_constitution_full(constitution, private_key_path) |
function | Sign constitution document |
verify_constitution_full(constitution, public_key_path) |
function | Verify constitution signature |
sign_receipt(receipt_dict, private_key_path) |
function | Sign receipt, add signature block |
verify_receipt_signature(receipt, public_key_path) |
function | Verify receipt signature |
Key filenames use the SHA-256 key fingerprint (not hardcoded sanna_ed25519).
Internal deps: hashing, utils.safe_json
External deps: base64, cryptography (Ed25519PrivateKey, Ed25519PublicKey)
Purpose: Canonical JSON (RFC 8785) serialization and deterministic hashing.
| Export | Type | Description |
|---|---|---|
canonical_json_bytes(obj) |
function | Sanna Canonical JSON → bytes |
hash_text(s, truncate=64) |
function | NFC-normalize → SHA-256 hex |
hash_obj(obj, truncate=64) |
function | canonical_json_bytes → SHA-256 hex |
canonicalize_text(s) |
function | NFC normalization for hashing |
normalize_floats(obj) |
function | Normalize floats for canonical hashing |
sha256_hex(data, truncate=64) |
function | Raw SHA-256 hex |
EMPTY_HASH |
constant | SHA-256 of empty bytes |
Internal: _reject_floats(obj, path) raises on NaN/Infinity.
External deps: hashlib, json, math, unicodedata
Purpose: C1-C5 check implementations, receipt assembly, and trace data extraction.
| Export | Type | Description |
|---|---|---|
CheckResult |
dataclass | check_id, name, passed, severity, evidence, details |
SannaReceipt |
dataclass | Full receipt model (spec_version through enforcement) |
Enforcement |
dataclass | Top-level enforcement outcome (v0.13.0+) |
FinalAnswerProvenance |
dataclass | Deprecated — where final answer was selected from |
ConstitutionProvenance |
dataclass | document_id, policy_hash, version, source |
HaltEvent |
dataclass | Deprecated — replaced by Enforcement |
generate_receipt(trace_data, checks, ...) |
function | Assemble receipt from trace data and check results |
select_final_answer(trace_data) |
function | Multi-step trace: select final answer with precedence |
extract_context(trace_data) |
function | Extract context string from trace |
extract_query(trace_data) |
function | Extract query string from trace |
extract_trace_data(trace) |
function | Generic context extraction (replaces Langfuse adapter) |
find_snippet(text, keywords, max_len) |
function | Extract relevant text snippet |
TOOL_VERSION |
constant | "1.5.0" (from version.py) |
SPEC_VERSION |
constant | "1.5" |
CHECKS_VERSION |
constant | "10" |
TOOL_NAME |
constant | "sanna" |
Check functions (private, with public aliases at module bottom):
_check_c1_context_contradiction(context, output, enforcement, structured_context)_check_c2_unmarked_inference(context, output, enforcement)_check_c3_false_certainty(context, output, enforcement)_check_c4_conflict_collapse(context, output, enforcement)_check_c5_premature_compression(context, output, enforcement)
Internal deps: hashing, version
External deps: uuid, dataclasses, datetime
Purpose: Runtime enforcement decorator and receipt generation pipeline.
| Export | Type | Description |
|---|---|---|
sanna_observe(...) |
decorator | Wraps agent function with governance checks |
SannaResult |
class | output + receipt wrapper, .status, .passed properties |
SannaHaltError |
exception | Raised on enforcement halt; carries .receipt |
build_trace_data(correlation_id, query, context, output) |
function | Build trace dict for generate_receipt |
generate_constitution_receipt(trace_data, check_configs, ...) |
function | Generate receipt from constitution-driven configs |
Key sanna_observe parameters: receipt_dir, store, constitution_path, constitution_public_key_path, private_key_path, identity_provider_keys, require_constitution_sig, error_policy, strict, on_violation, checks, halt_on.
Key internals: _resolve_inputs() (maps function args to context/query), _post_execution_governance() (shared sync/async governance logic), _run_reasoning_gate() / _run_reasoning_gate_async() (pre-execution reasoning checks), _write_receipt() (atomic file write).
Internal deps: receipt, hashing, constitution, enforcement, crypto, store, utils.safe_io, utils.crypto_validation, utils.sanitize, reasoning.pipeline (optional)
External deps: functools, inspect, asyncio, concurrent.futures
Purpose: Offline receipt verification without network access.
| Export | Type | Description |
|---|---|---|
VerificationResult |
dataclass | valid, exit_code, errors, warnings, fingerprints, status |
TriadVerification |
dataclass | Receipt Triad hash verification result |
load_schema(schema_path) |
function | Load receipt JSON schema |
verify_receipt(receipt, schema, ...) |
function | Full multi-step verification |
verify_receipt_triad(receipt) |
function | Verify Receipt Triad hashes |
verify_constitution_chain(receipt, constitution_path, ...) |
function | Verify receipt→constitution binding; returns (errors, warnings) |
Verification steps (in order):
verify_schema()— JSON schema validationverify_hash_format()— SHA-256 hex formatverify_content_hashes()— recompute context_hash + output_hashverify_fingerprint()→ dispatches to_verify_fingerprint_v013()or_verify_fingerprint_legacy()verify_status_consistency()— PASS/WARN/FAIL logicverify_check_counts()— passed + failed match actualsverify_constitution_hash()— policy_hash valid hex- Signature verification (optional, with
--public-key) verify_constitution_chain()+_verify_approval_chain()
Internal deps: hashing, constitution, crypto, utils.safe_json
External deps: json, re, jsonschema
All implemented as private functions in receipt.py. Each takes (context, output, enforcement) and returns CheckResult.
| Check | Function | What It Detects |
|---|---|---|
| C1 | _check_c1_context_contradiction |
Output claims that contradict provided context |
| C2 | _check_c2_unmarked_inference |
Inferences/speculation presented as fact |
| C3 | _check_c3_false_certainty |
Certainty language exceeding evidence strength |
| C4 | _check_c4_conflict_collapse |
Conflicting evidence collapsed into single conclusion |
| C5 | _check_c5_premature_compression |
Nuanced input reduced to unconditional output |
C1 has two modes: _c1_flat() (plain string context) and _c1_source_aware() (structured context with trust tiers).
Note: There is no src/sanna/checks/ directory. These functions are all in receipt.py.
Purpose: Re-exports from submodules. Exports: CheckConfig, CustomInvariantRecord, configure_checks, INVARIANT_CHECK_MAP, CHECK_REGISTRY, AuthorityDecision, evaluate_authority, EscalationTarget, EscalationResult, execute_escalation, register_escalation_callback, clear_escalation_callbacks, get_escalation_callback.
Purpose: Maps constitution invariants to check functions.
| Export | Type | Description |
|---|---|---|
CheckConfig |
dataclass | check_id, check_fn, enforcement_level, triggered_by, check_impl, source |
CustomInvariantRecord |
dataclass | invariant_id, rule, enforcement, status, reason |
configure_checks(constitution) |
function | Returns (list[CheckConfig], list[CustomInvariantRecord]) |
CHECK_REGISTRY |
dict | Maps sanna.* namespaced IDs → check functions |
INVARIANT_CHECK_MAP |
dict | Maps INV_* IDs → (check_impl_id, check_fn) tuples |
Resolution order: explicit check: field → INVARIANT_CHECK_MAP → custom evaluator registry → NOT_CHECKED.
Standard mappings:
| Invariant ID | Check Implementation |
|---|---|
INV_NO_FABRICATION |
sanna.context_contradiction |
INV_MARK_INFERENCE |
sanna.unmarked_inference |
INV_NO_FALSE_CERTAINTY |
sanna.false_certainty |
INV_PRESERVE_TENSION |
sanna.conflict_collapse |
INV_NO_PREMATURE_COMPRESSION |
sanna.premature_compression |
Internal deps: receipt (CheckResult + check functions), evaluators, constitution
Purpose: Authority boundary evaluation engine.
| Export | Type | Description |
|---|---|---|
AuthorityDecision |
dataclass | decision (halt/allow/escalate), reason, boundary_type, escalation_target |
evaluate_authority(action, params, constitution) |
function | Evaluate action against boundaries → AuthorityDecision |
normalize_authority_name(name) |
function | NFKC + camelCase splitting for matching |
Evaluation order: cannot_execute → must_escalate → can_execute → default allow.
Key internal: _matches_action(pattern, action) — bidirectional substring matching with separator normalization and _STOP_WORDS exclusion.
Internal deps: constitution, escalation
Purpose: Runtime execution of escalation actions.
| Export | Type | Description |
|---|---|---|
EscalationTarget |
dataclass | type (log/webhook/callback), url, handler |
EscalationResult |
dataclass | success, target_type, details |
execute_escalation(target, event_details) |
function | Synchronous escalation execution |
async_execute_escalation(target, event_details) |
function | Async variant (httpx or threading fallback) |
register_escalation_callback(name, handler) |
function | Register named callback |
clear_escalation_callbacks() |
function | Clear all registered callbacks |
get_escalation_callback(name) |
function | Look up registered callback |
Key internals: _validate_escalation_url() (SSRF validation), _execute_webhook() / _execute_webhook_async() (HTTP POST), _webhook_threaded_fallback() (stdlib urllib, daemon thread, no-redirect handler).
Note: This module's webhook implementation is independent from gateway/server.py:_deliver_token_via_webhook(). Security hardening must be applied to both.
External deps: logging, datetime, dataclasses
Purpose: Global evaluator registry with decorator-based registration.
| Export | Type | Description |
|---|---|---|
register_invariant_evaluator(invariant_id) |
decorator | Register evaluator function for an invariant ID |
get_evaluator(invariant_id) |
function | Look up registered evaluator |
list_evaluators() |
function | List all registered invariant IDs |
clear_evaluators() |
function | Clear registry (tests MUST call this in fixtures) |
Module-level _EVALUATOR_REGISTRY dict. Raises on duplicate registration unless using idempotent register_llm_evaluators() path.
Purpose: LLM-as-Judge semantic evaluators using Anthropic API via stdlib urllib.
| Export | Type | Description |
|---|---|---|
LLMJudge |
class | Evaluator using Claude API; .evaluate(check_id, context, output, constitution) → CheckResult |
LLMEvaluationError |
exception | Raised on LLM evaluation failure (not false CheckResult) |
register_llm_evaluators(judge, checks) |
function | Idempotent registration of LLM evaluators |
enable_llm_checks(api_key, model, checks) |
function | Convenience factory for LLM evaluators |
LLM invariant aliases: LLM_C1→INV_LLM_CONTEXT_GROUNDING, LLM_C2→INV_LLM_FABRICATION_DETECTION, LLM_C3→INV_LLM_INSTRUCTION_ADHERENCE, LLM_C4→INV_LLM_FALSE_CERTAINTY, LLM_C5→INV_LLM_PREMATURE_COMPRESSION.
Prompts are hardened with <audit> tag wrapping and XML entity escaping via escape_audit_content().
Internal deps: receipt.CheckResult, evaluators, utils.safe_json, utils.sanitize
External deps: json, os, urllib.request
Purpose: Abstract judge interface (provider-agnostic).
| Export | Type | Description |
|---|---|---|
JudgeVerdict |
enum | PASS, FAIL, ERROR, HEURISTIC |
JudgeResult |
dataclass | score, verdict, method, explanation, latency_ms, error_detail |
BaseJudge |
ABC | Abstract: async evaluate(tool_name, arguments, justification, invariant_id) → JudgeResult |
Purpose: Deterministic fallback judge with no API calls (four sub-checks).
| Export | Type | Description |
|---|---|---|
HeuristicJudge(BaseJudge) |
class | Presence + substance + parroting + length checks |
Default _DEFAULT_MIN_LENGTH = 20, default _DEFAULT_BLOCKLIST of boilerplate phrases.
Purpose: Judge instantiation with provider auto-detection and fallback.
| Export | Type | Description |
|---|---|---|
JudgeFactory |
class (static) | .create(provider, model, api_key, error_policy, cross_provider, agent_provider) → BaseJudge |
NoProviderAvailableError |
exception | Raised when no LLM provider can be configured |
Provider detection checks ANTHROPIC_API_KEY and OPENAI_API_KEY environment variables.
Purpose: Orchestrates GLC reasoning checks for tool call justifications.
| Export | Type | Description |
|---|---|---|
ReasoningPipeline |
class | .evaluate(tool_name, args, enforcement_level) → ReasoningEvaluation |
Runs checks in order: GLC-001 (presence) → GLC-002 (substance) → GLC-003 (parroting) → GLC-005 (LLM coherence, conditional). Produces assurance level: full / partial / none.
Internal deps: constitution, gateway.receipt_v2, reasoning.checks.*, reasoning.judge, reasoning.judge_factory
Purpose: High-level facade for reasoning evaluation.
| Export | Type | Description |
|---|---|---|
ReasoningEvaluator |
class | .evaluate(tool_name, args, enforcement_level) → ReasoningEvaluation; .strip_justification(args) |
Purpose: LLM judge implementations for Anthropic and OpenAI.
| Export | Type | Description |
|---|---|---|
AnthropicJudge(BaseJudge) |
class | Claude API judge via httpx |
OpenAIJudge(BaseJudge) |
class | OpenAI API judge via httpx |
AnthropicClient(LLMClient) |
class | Legacy client interface |
create_llm_client(provider, api_key, model) |
function | Legacy factory |
Both judges use <audit> tag wrapping and escape_audit_content() for prompt hardening. System prompts have standard and thorough scrutiny levels.
External deps: httpx (optional, guarded)
| Module | Class | Check |
|---|---|---|
base.py |
Check(ABC) |
Abstract base with run(justification, tool_name, args) |
glc_001_presence.py |
JustificationPresenceCheck |
GLC-001: Justification exists and non-empty |
glc_002_substance.py |
MinimumSubstanceCheck |
GLC-002: Meets minimum length/content threshold |
glc_003_parroting.py |
NoParrotingCheck |
GLC-003: Not boilerplate/parroted text |
glc_005_coherence.py |
LLMCoherenceCheck |
GLC-005: LLM evaluates reasoning coherence |
Purpose: MCP enforcement proxy — transparent tool forwarding with constitution enforcement, receipt generation, escalation handling, and circuit breaker health.
| Export | Type | Description |
|---|---|---|
SannaGateway |
class | Main gateway: FastMCP server, multi-downstream, enforcement |
SannaGateway.for_single_server(name, command, ...) |
classmethod | Preferred factory for single downstream |
DownstreamSpec |
dataclass | Per-downstream config (name, command, args, env, timeout, policies) |
EscalationStore |
class | Pending escalations with TTL purge, capacity limits, HMAC tokens |
PendingEscalation |
dataclass | Held tool call awaiting approval |
CircuitState |
enum | CLOSED, OPEN, HALF_OPEN |
DuplicateToolError |
exception | Two downstreams register same prefixed tool name |
run_gateway() |
function | Parse --config and start gateway on stdio |
Key SannaGateway methods:
async start()— connect to downstreams, discover tools, load constitutionasync shutdown()— disconnect all downstreamsasync run_stdio()— start, serve, shutdown
Meta-tools (not prefixed):
sanna_escalation_respond— user approval/denial formust_escalatecallssanna_gateway_status— gateway health and connection status
Key internals: _resolve_policy() (cascade: per-tool → default_policy → constitution), _generate_receipt() (signed receipt with authority decisions), _compute_approval_token() (HMAC-SHA256), _extract_result_text() (safe MCP result extraction), _apply_redaction_markers() (PII redaction).
Internal deps: gateway.mcp_client, gateway.config, constitution, enforcement, middleware, crypto, hashing, utils.safe_io, utils.safe_json, utils.crypto_validation
External deps: mcp (FastMCP, types, stdio_server), asyncio, hmac, hashlib
Purpose: MCP client for downstream stdio server connections.
| Export | Type | Description |
|---|---|---|
DownstreamConnection |
class | Spawns child process, MCP handshake, tool discovery, call forwarding |
DownstreamError |
exception | Base error |
DownstreamConnectionError |
exception | Connection/initialization failure |
DownstreamTimeoutError |
exception | Operation timeout |
Key methods: async connect(), async close(), async reconnect(), async list_tools(), async call_tool(name, arguments). call_tool never raises — errors captured in CallToolResult.
External deps: mcp (ClientSession, StdioServerParameters, stdio_client)
Purpose: Gateway YAML config loader with validation and env var interpolation.
| Export | Type | Description |
|---|---|---|
GatewayConfig |
dataclass | Top-level parsed config |
DownstreamConfig |
dataclass | Per-downstream server config |
ToolPolicyConfig |
dataclass | Per-tool policy override |
RedactionConfig |
dataclass | PII redaction controls |
GatewayConfigError |
exception | Invalid configuration |
load_gateway_config(config_path) |
function | Load and validate YAML → GatewayConfig |
resolve_tool_policy(tool_name, downstream) |
function | Cascade policy resolution |
build_policy_overrides(downstream) |
function | Flat policy overrides dict |
validate_webhook_url(url) |
function | SSRF validation |
Supports ${VAR_NAME} interpolation in env blocks. SSRF validation rejects localhost, private IPs, link-local, multicast, and cloud metadata endpoints.
Internal deps: utils.safe_yaml
External deps: yaml, ipaddress, os, re
Purpose: Injects _justification parameter into downstream tool schemas.
| Export | Type | Description |
|---|---|---|
mutate_tool_schema(tool_dict, constitution, ...) |
function | Add _justification param based on enforcement level |
Purpose: Receipt v2.0 models: Receipt Triad and reasoning evaluation results.
| Export | Type | Description |
|---|---|---|
ReceiptTriad |
dataclass | input_hash, reasoning_hash, action_hash, context_limitation |
GatewayCheckResult |
dataclass | check_id, method, passed, confidence, evidence, assurance_level |
RECEIPT_VERSION_2 |
constant | "2.0" |
MAX_STORED_PAYLOAD_BYTES |
constant | 65536 (configurable via env) |
Purpose: One-command migration from existing MCP client configs to governed gateway.
| Export | Type | Description |
|---|---|---|
migrate_command(args) |
function | CLI entry for sanna-gateway migrate |
ServerEntry |
dataclass | Parsed MCP server entry from client config |
MigrationPlan |
dataclass | Computed migration plan before execution |
Detects secrets (sk-, ntn_, ghp_, etc.) and replaces with ${VAR} interpolation. Supports Claude Desktop adapter (macOS/Linux/Windows).
Purpose: FastMCP server exposing 7 governance tools over stdio transport.
| Tool | Parameters | Description |
|---|---|---|
sanna_verify_receipt |
receipt_json |
Verify receipt offline |
sanna_generate_receipt |
query, context, response, constitution_path? |
Generate receipt |
sanna_list_checks |
(none) | List C1-C5 checks with descriptions |
sanna_evaluate_action |
action_name, action_params, constitution_path |
Evaluate against authority boundaries |
sanna_query_receipts |
db_path?, agent_id?, status?, since?, until?, halt_only?, limit?, analysis? |
Query receipts or run drift analysis |
sanna_check_constitution_approval |
constitution_path, author_public_key_path?, approver_public_key_path? |
Check approval status |
sanna_verify_identity_claims |
constitution_path, provider_keys? |
Verify identity claims |
Size guards: MAX_RECEIPT_JSON_SIZE (1 MB), MAX_CONTEXT_SIZE (500 KB), MAX_RESPONSE_SIZE (500 KB), MAX_ACTION_SIZE (10 KB), MAX_QUERY_LIMIT (500).
Internal deps: verify, middleware, constitution, enforcement, crypto, store, drift
External deps: mcp (FastMCP), pydantic (BaseModel, Field)
Purpose: SQLite-backed receipt persistence with WAL mode.
| Export | Type | Description |
|---|---|---|
ReceiptStore |
class | SQLite receipt storage: .save(receipt), .query(filters), .close() |
Features: schema version validation, WAL mode, file permission hardening (0o600), symlink rejection, fd-based ownership check, combinable query filters, LIMIT/OFFSET.
_SCHEMA_VERSION = 1. Context manager support via __enter__ / __exit__.
Internal deps: utils.safe_json
External deps: sqlite3, threading
Purpose: Governance drift analytics using linear regression over stored receipts.
| Export | Type | Description |
|---|---|---|
DriftAnalyzer |
class | .analyze(window_days, threshold) → DriftReport; .analyze_multi(windows) |
DriftReport |
dataclass | Fleet-level: window_days, threshold, agents, fleet_status |
AgentDriftSummary |
dataclass | Per-agent: total_receipts, checks, projected_breach_days |
CheckDriftDetail |
dataclass | Per-check: fail_rate, trend_slope, projected_breach_days |
calculate_slope(xs, ys) |
function | Least-squares linear regression |
project_breach(current_rate, slope, threshold) |
function | Days until threshold |
format_drift_report(report) |
function | Human-readable text output |
export_drift_report(report, fmt) |
function | JSON or CSV string |
export_drift_report_to_file(report, path, fmt) |
function | Write to file |
Internal deps: store
External deps: csv, json, math
Purpose: Self-contained verification archives (zip) with 7-step verification.
| Export | Type | Description |
|---|---|---|
BundleCheck |
dataclass | Single verification step: name, passed, detail |
BundleVerificationResult |
dataclass | valid, checks, receipt_summary, errors |
create_bundle(receipt_path, constitution_path, public_key_path, output_path, ...) |
function | Create evidence bundle zip |
verify_bundle(bundle_path, constitution_path?) |
function | 7-step verification |
BUNDLE_FORMAT_VERSION |
constant | "1.0.0" |
7 verification steps: (1) bundle structure, (2) receipt schema, (3) receipt fingerprint, (4) constitution signature, (5) provenance chain, (6) receipt signature, (7) approval verification.
Receipt and constitution keys are resolved independently by key_id from the public_keys/ directory within the bundle.
Internal deps: crypto, constitution, verify, utils.safe_json, utils.safe_yaml
External deps: zipfile, tempfile
Purpose: All CLI entry points (except sanna-init and sanna-mcp).
| Function | Command | Description |
|---|---|---|
main_sanna() |
sanna |
Unified dispatcher for all subcommands |
main_generate() |
sanna-generate |
Generate receipt from trace-data JSON |
main_verify() |
sanna-verify |
Verify receipt integrity + signature + provenance |
main_keygen() |
sanna-keygen |
Generate Ed25519 keypair (--label support) |
main_sign_constitution() |
sanna-sign-constitution |
Sign constitution |
main_verify_constitution() |
sanna-verify-constitution |
Verify constitution signature |
main_create_bundle() |
sanna-create-bundle |
Create evidence bundle |
main_verify_bundle() |
sanna-verify-bundle |
Verify evidence bundle |
main_drift_report() |
sanna-drift-report |
Fleet drift analysis |
approve_constitution_cmd() |
sanna-approve-constitution |
Approve signed constitution |
diff_cmd() |
sanna-diff |
Diff two constitutions |
main_demo() |
sanna-demo |
Self-contained governance demo |
main_inspect() |
sanna-inspect |
Pretty-print receipt |
main_check_config() |
sanna-check-config |
Validate gateway config (dry-run) |
format_receipt_summary(receipt) |
(library) | Human-readable receipt formatting |
Internal deps: receipt, verify, constitution, crypto, bundle, store, drift, constitution_diff, utils.safe_json
External deps: argparse, json, sys
Purpose: Interactive constitution generator with template selection.
| Export | Type | Description |
|---|---|---|
main() |
function | Interactive wizard (sanna-init entry point) |
prompt_for_template() |
function | Template selection prompt |
generate_constitution(template_key, agent_name, ...) |
function | Generate from template |
load_template(template_key) |
function | Load YAML from importlib.resources |
Templates loaded via importlib.resources from src/sanna/templates/.
Purpose: OpenTelemetry bridge: receipt → OTel span with pointer + integrity hash.
| Export | Type | Description |
|---|---|---|
receipt_to_span(receipt, tracer, artifact_uri?) |
function | Create OTel span from receipt metadata |
SannaOTelExporter(SpanExporter) |
class | Custom SpanExporter filtering for sanna.receipt.id attribute |
15 sanna.* span attributes. Span name: sanna.governance.evaluation. Content hash uses canonical_json_bytes() for cross-verifier parity.
Internal deps: hashing
External deps: opentelemetry (optional, guarded)
Pluggable receipt persistence abstraction introduced in v1.0.0.
| Export | Type | Description |
|---|---|---|
ReceiptSink |
ABC | store(receipt), batch_store(receipts), flush(), close() |
SinkResult |
dataclass | stored, failed, errors, ok (property) |
FailurePolicy |
enum | LOG_AND_CONTINUE, RAISE, BUFFER_AND_RETRY |
NullSink |
class | Always succeeds (testing/no-op) |
LocalSQLiteSink |
class | Wraps ReceiptStore for SQLite persistence |
CloudHTTPSink |
class | HTTP POST to /v1/receipts; stdlib urllib; exponential backoff with jitter; JSONL buffer for BUFFER_AND_RETRY |
CompositeSink |
class | Fan-out to multiple sinks with failure isolation |
@sanna_observe gains sink= parameter. Gateway gains configure_sink() method and receipt_sink YAML config block.
Internal deps: store, utils.safe_io, utils.safe_json
Runtime patching of subprocess and HTTP libraries to enforce governance on calls made from within an observed agent function. Introduced in v1.1.0.
| Export | Type | Description |
|---|---|---|
patch_subprocess() |
function | Patches subprocess, os.exec*, os.spawn*, os.popen |
unpatch_subprocess() |
function | Restores original subprocess functions |
patch_http() |
function | Patches urllib.request and http.client |
unpatch_http() |
function | Restores original HTTP functions |
Key internals: shell metacharacter bypass prevention (SAN-35), TOCTOU-safe binary path resolution (SAN-44), wrapper script bypass detection (SAN-45), thread-safe restore (SAN-46), env var manipulation bypass prevention (SAN-47).
Internal deps: enforcement, receipt
| Export | Type | Description |
|---|---|---|
atomic_write_sync(target_path, data, mode=0o600) |
function | Atomic write with symlink check, fsync, os.replace |
atomic_write_text_sync(target_path, text, mode=0o600) |
function | Text convenience wrapper |
ensure_secure_dir(dir_path, mode=0o700) |
function | Create/validate directory with symlink rejection |
SecurityError |
exception | Security check failure |
| Export | Type | Description |
|---|---|---|
safe_json_loads(s) |
function | Rejects duplicate keys and NaN/Infinity |
safe_json_load(fp) |
function | File variant |
| Export | Type | Description |
|---|---|---|
safe_yaml_load(stream) |
function | YAML parsing with duplicate key detection |
| Export | Type | Description |
|---|---|---|
escape_audit_content(text) |
function | XML entity escaping (&, <, >) for LLM prompt injection prevention |
| Export | Type | Description |
|---|---|---|
is_valid_signature_structure(sig) |
function | Non-empty, valid base64, exactly 64 bytes |
ED25519_SIGNATURE_LENGTH |
constant | 64 |
User creates YAML constitution
│
▼
constitution.py:load_constitution(path)
├── utils/safe_yaml.py:safe_yaml_load() — parse YAML, reject duplicate keys
├── constitution.py:validate_against_schema() — JSON schema validation
├── constitution.py:validate_constitution_data() — semantic validation
└── constitution.py:parse_constitution(data) — dict → Constitution dataclass
│
▼
constitution.py:sign_constitution(constitution, private_key_path)
└── crypto.py:sign_constitution_full() — Ed25519 sign canonical JSON
│
▼
enforcement/constitution_engine.py:configure_checks(constitution)
├── For each invariant:
│ ├── Check explicit `check:` field → CHECK_REGISTRY lookup
│ ├── Check INVARIANT_CHECK_MAP (INV_* → sanna.* check fn)
│ ├── Check evaluators/__init__.py:get_evaluator() (custom)
│ └── Fallback → CustomInvariantRecord(status="NOT_CHECKED")
└── Returns (list[CheckConfig], list[CustomInvariantRecord])
│
▼
middleware.py:sanna_observe() decorator — wraps agent function
├── _resolve_inputs() — extract context/query from function args
├── _run_reasoning_gate() — pre-execution reasoning checks (if reasoning config)
├── Execute wrapped function
├── generate_constitution_receipt() — run all configured checks
│ ├── receipt.py:generate_receipt() — assemble receipt
│ ├── enforcement/authority.py:evaluate_authority() — if action evaluation needed
│ └── crypto.py:sign_receipt() — Ed25519 sign receipt
├── _write_receipt() — atomic write to receipt_dir
└── Halt / warn / return SannaResult
Agent function returns output
│
▼
middleware.py:generate_constitution_receipt()
├── receipt.py:extract_context(), extract_query()
├── hashing.py:hash_text(context) → context_hash
├── hashing.py:hash_text(output) → output_hash
├── Run each CheckConfig.check_fn(context, output, enforcement)
│ └── Returns CheckResult per check
├── receipt.py:generate_receipt()
│ ├── Compute checks_hash = hash_obj(checks list)
│ ├── Compute constitution_hash (strip constitution_approval first)
│ ├── Compute halt_hash, coverage_hash
│ ├── Build fingerprint: "{trace_id}|{context_hash}|{output_hash}|..."
│ │ └── Conditionally append authority/escalation/trust/extensions hashes
│ ├── hashing.py:hash_text(fingerprint_input) → receipt_fingerprint
│ └── Assemble SannaReceipt
└── crypto.py:sign_receipt(receipt_dict, private_key_path)
├── Remove signature.value from signing material
├── hashing.py:canonical_json_bytes(receipt)
└── crypto.py:sign_bytes(canonical_bytes, private_key) → base64 signature
│
▼
JSON receipt written to disk
│
▼
verify.py:verify_receipt(receipt, schema, public_key_path)
├── verify_schema() — jsonschema validation
├── verify_hash_format() — SHA-256 hex pattern check
├── verify_content_hashes() — recompute context_hash + output_hash
├── verify_fingerprint() — recompute full fingerprint, compare
├── verify_status_consistency() — PASS/WARN/FAIL matches checks
├── verify_check_counts() — passed + failed tally
├── verify_constitution_hash() — policy_hash valid hex
├── crypto.py:verify_receipt_signature() — Ed25519 verify (optional)
└── verify_constitution_chain() — receipt→constitution binding (optional)
└── _verify_approval_chain() — content_hash + approval signature
MCP Client (Claude Desktop / Claude Code)
│ (MCP stdio)
▼
gateway/server.py:SannaGateway — FastMCP server
├── start() — connect to all downstreams, discover tools
│ └── gateway/mcp_client.py:DownstreamConnection.connect()
│ ├── Spawn child process (e.g., npx @notionhq/notion-mcp-server)
│ └── MCP handshake → list_tools()
│
├── Tool call arrives (e.g., "notion_API-patch-page")
│ │
│ ├── Strip prefix → resolve downstream + original tool name
│ │
│ ├── _resolve_policy(tool_name, ds_state)
│ │ ├── Per-tool override (exact match on unprefixed name)
│ │ ├── Server default_policy
│ │ └── enforcement/authority.py:evaluate_authority() against constitution
│ │
│ ├── Policy = "cannot_execute":
│ │ ├── _generate_receipt(tool_name, args, error_result)
│ │ └── Return denial tool result
│ │
│ ├── Policy = "must_escalate":
│ │ ├── reasoning/pipeline.py:ReasoningPipeline.evaluate() (if reasoning config)
│ │ ├── EscalationStore.create_async() — store pending escalation
│ │ ├── _compute_approval_token() — HMAC-SHA256
│ │ ├── _deliver_token() — stderr / file / webhook
│ │ ├── _generate_receipt() — escalation receipt
│ │ └── Return structured approval prompt to client
│ │ │
│ │ ▼ (client sends sanna_escalation_respond)
│ │ ├── Validate token (HMAC + expiry)
│ │ ├── Forward to downstream: DownstreamConnection.call_tool()
│ │ ├── _generate_receipt() — approved execution receipt
│ │ └── Return downstream result
│ │
│ └── Policy = "can_execute":
│ ├── reasoning/pipeline.py:ReasoningPipeline.evaluate() (if reasoning config)
│ ├── Forward: DownstreamConnection.call_tool(original_name, arguments)
│ ├── _generate_receipt() — success receipt
│ └── Return downstream result to client
│
└── Circuit breaker:
├── Consecutive failures → OPEN state
├── Cooldown period → HALF_OPEN (single probe)
└── Probe success → CLOSED
$ sanna verify receipt.json --constitution const.yaml --public-key key.pub
│
▼
cli.py:main_sanna()
└── Dispatches to: cli.py:main_verify()
├── argparse — parse CLI flags
├── utils/safe_json.py:safe_json_loads() — load receipt JSON
├── verify.py:load_schema() — load receipt.schema.json
├── verify.py:verify_receipt(receipt, schema, public_key_path)
│ └── (see §3.2 verification steps)
├── verify.py:verify_constitution_chain(receipt, constitution_path, ...)
│ ├── constitution.py:load_constitution()
│ ├── Verify receipt→constitution document_id binding
│ ├── crypto.py:verify_constitution_full() — check constitution signature
│ └── _verify_approval_chain() — approval integrity
├── If --json: output JSON result
└── Print human-readable verification report; sys.exit(exit_code)
Inputs: context (str or structured list), output (str)
│
▼
C1 — receipt.py:_check_c1_context_contradiction(context, output, enforcement, structured_context)
├── If structured_context: _c1_source_aware()
│ ├── For each source in context:
│ │ ├── Extract claims from source text
│ │ ├── Check trust tier (tier_1 fully trusted, untrusted excluded)
│ │ └── Compare against output claims
│ └── Flag contradictions with source attribution
└── Else: _c1_flat()
├── Extract key factual claims from context
├── Compare each claim against output
└── Flag: output asserts opposite of context
│
▼
C2 — receipt.py:_check_c2_unmarked_inference(context, output, enforcement)
├── Identify inference/speculation phrases in output
├── Check if properly hedged ("I think", "likely", "based on")
└── Flag: assertions that extend beyond context without qualification
│
▼
C3 — receipt.py:_check_c3_false_certainty(context, output, enforcement)
├── Detect certainty language ("definitely", "certainly", "always")
├── Check if evidence supports that level of certainty
└── Flag: certainty exceeds evidence strength
│
▼
C4 — receipt.py:_check_c4_conflict_collapse(context, output, enforcement)
├── Detect conflicting positions/data in context
├── Check if output acknowledges the conflict
└── Flag: conflicting evidence collapsed into single conclusion
│
▼
C5 — receipt.py:_check_c5_premature_compression(context, output, enforcement)
├── Detect nuanced/conditional input (mixed evidence, caveats)
├── Check if output preserves nuance
└── Flag: conditional input → unconditional conclusion
│
▼
Each check returns: CheckResult(check_id, name, passed, severity, evidence, details)
│
▼
receipt.py:generate_receipt() — aggregate results
├── Count: checks_passed, checks_failed (excluding NOT_CHECKED, ERRORED)
├── Compute status:
│ ├── Any failed + severity ≥ critical → "FAIL"
│ ├── Any failed + severity < critical → "WARN"
│ └── All passed → "PASS"
└── Build enforcement decision (halt/warn/allow)
| Dataclass | Key Fields | Notes |
|---|---|---|
Constitution |
schema_version, identity, provenance, boundaries, invariants, authority_boundaries, trusted_sources, halt_conditions, approval, reasoning | Top-level model |
AgentIdentity |
agent_name, domain, description, extensions, identity_claims | Extensions dict is flattened for signing |
IdentityClaim |
provider, claim_type, credential_id, issued_at, expires_at, signature, public_key_id | Signed with canonical JSON |
Provenance |
authored_by, approved_by, approval_date, approval_method, change_history, signature | |
ConstitutionSignature |
value, key_id, signed_by, signed_at, scheme | scheme = "constitution_sig_v1" |
Boundary |
id (B###), description, category, severity | |
Invariant |
id, rule, enforcement, check | check is optional explicit check ID |
HaltCondition |
id (H###), trigger, escalate_to, severity, enforcement | |
AuthorityBoundaries |
cannot_execute, must_escalate, can_execute, default_escalation | must_escalate items are EscalationRule |
EscalationRule |
condition, target | Not hashable (no set()) |
EscalationTargetConfig |
type, url, handler | type: log/webhook/callback |
TrustedSources |
tier_1, tier_2, tier_3, untrusted | All lists of strings |
ApprovalRecord |
status, approver_id, approver_role, approved_at, approval_signature, constitution_version, content_hash | |
ApprovalChain |
records | .current property, .is_approved property |
ReasoningConfig |
require_justification_for, on_missing_justification, checks, judge, ... | v1.1 constitution extension |
| Dataclass | Key Fields | Notes |
|---|---|---|
SannaReceipt |
spec_version, tool_version, checks_version, receipt_id, receipt_fingerprint, full_fingerprint, correlation_id, timestamp, inputs, outputs, context_hash, output_hash, checks, checks_passed, checks_failed, status, constitution_ref, enforcement, parent_receipts, workflow_id, content_mode, content_mode_source (v1.0.0+), enforcement_surface, invariants_scope (v1.3+), tool_name, agent_model, agent_model_provider, agent_model_version (v1.4+), agent_identity (v1.5+) | v1.5 format; 29 fields (adds agent_identity) |
CheckResult |
check_id, name, passed, severity, evidence, details | |
Enforcement |
action, reason, failed_checks, enforcement_mode, timestamp | v0.13.0+ replaces HaltEvent |
ConstitutionProvenance |
document_id, policy_hash, version, source | |
HaltEvent |
halted, reason, failed_checks, timestamp, enforcement_mode | Deprecated |
FinalAnswerProvenance |
source, span_id, span_name, field | Deprecated |
| Dataclass | Key Fields | Notes |
|---|---|---|
CheckConfig |
check_id, check_fn, enforcement_level, triggered_by, check_impl, source | |
CustomInvariantRecord |
invariant_id, rule, enforcement, status, reason | status defaults "NOT_CHECKED" |
AuthorityDecision |
decision, reason, boundary_type, escalation_target | decision: halt/allow/escalate |
EscalationTarget |
type, url, handler | |
EscalationResult |
success, target_type, details |
| Dataclass | Key Fields | Notes |
|---|---|---|
VerificationResult |
valid, exit_code, errors, warnings, computed_fingerprint, expected_fingerprint, computed_status, expected_status | |
TriadVerification |
present, input_hash_valid, reasoning_hash_valid, action_hash_valid, errors, warnings |
| Dataclass | Key Fields | Notes |
|---|---|---|
BundleCheck |
name, passed, detail | Single verification step |
BundleVerificationResult |
valid, checks, receipt_summary, errors |
| Dataclass | Key Fields | Notes |
|---|---|---|
DriftReport |
window_days, threshold, generated_at, agents, fleet_status | |
AgentDriftSummary |
agent_id, constitution_id, status, total_receipts, checks, projected_breach_days | |
CheckDriftDetail |
check_id, total_evaluated, pass_count, fail_count, fail_rate, trend_slope, projected_breach_days, status |
| Dataclass | Key Fields | Notes |
|---|---|---|
DiffEntry |
category, change_type, key, old_value, new_value | change_type: added/removed/modified |
DiffResult |
entries, old_version, new_version, old_hash, new_hash | .to_text(), .to_json(), .to_markdown() |
| Dataclass | Key Fields | Notes |
|---|---|---|
DownstreamSpec |
name, command, args, env, timeout, policy_overrides, default_policy, optional | |
PendingEscalation |
escalation_id, prefixed_name, original_name, arguments, server_name, reason, token_hash, status | Serializable via .to_dict() / .from_dict() |
ReceiptTriad |
input_hash, reasoning_hash, action_hash, context_limitation | |
GatewayCheckResult |
check_id, method, passed, confidence, evidence, assurance_level |
| Type | Key Fields | Notes |
|---|---|---|
JudgeVerdict (enum) |
PASS, FAIL, ERROR, HEURISTIC | |
JudgeResult (dataclass) |
score, verdict, method, explanation, latency_ms, error_detail | |
BaseJudge (ABC) |
async evaluate(...) → JudgeResult |
Source: src/sanna/spec/constitution.schema.json
# Required root fields:
sanna_constitution: "1.0.0" # Schema version (optional in schema, recommended)
identity: # REQUIRED
agent_name: string (minLength: 1) # REQUIRED — unique agent identifier
domain: string (minLength: 1) # REQUIRED — business domain
description: string # optional
identity_claims: [] # optional — verifiable claims from providers
# additionalProperties: allowed (flattened for signing)
provenance: # REQUIRED
authored_by: string (minLength: 1) # REQUIRED
approved_by: string | string[] # REQUIRED
approval_date: string (YYYY-MM-DD) # REQUIRED
approval_method: string # REQUIRED
change_history: [] # optional
signature: # optional — Ed25519 signature block
value: string | null # base64-encoded signature
key_id: string | null # ^[a-f0-9]{64}$ SHA-256 fingerprint
signed_by: string | null
signed_at: string | null
scheme: "constitution_sig_v1"
boundaries: # REQUIRED (minItems: 1)
- id: string (^B\d{3}$) # REQUIRED
description: string # REQUIRED
category: scope|authorization|confidentiality|safety|compliance|custom # REQUIRED
severity: critical|high|medium|low|info # REQUIRED
invariants: [] # optional
- id: string # REQUIRED — INV_*, custom
rule: string # REQUIRED — human-readable
enforcement: halt|warn|log # REQUIRED
check: string | null # optional — explicit check implementation
halt_conditions: [] # optional
- id: string (^H\d{3}$) # REQUIRED
trigger: string # REQUIRED
escalate_to: string # REQUIRED
severity: critical|high|medium|low|info # REQUIRED
enforcement: halt|warn|log # REQUIRED
authority_boundaries: # optional
cannot_execute: [string]
must_escalate: # list of condition/target pairs
- condition: string
target: {type: log|webhook|callback, url?: string, handler?: string}
can_execute: [string]
trusted_sources: # optional (or null)
tier_1: [string] # full trust
tier_2: [string] # evidence with verification flag
tier_3: [string] # reference only
untrusted: [string] # excluded from C1
escalation_targets: # optional
default: log|webhook|callback
policy_hash: string | null # ^[a-f0-9]{64}$
approval: # optional (or null)
records:
- status: approved|pending|revoked
approver_id: string
approver_role: string
approved_at: string
approval_signature: string # base64 Ed25519 signature
constitution_version: string
content_hash: string # ^[a-f0-9]{64}$
previous_version_hash: string | null
reasoning: # optional (v1.1+)
require_justification_for: [string]
on_missing_justification: block|warn|allow
on_check_error: block|warn|allow
on_api_error: block|warn|allow
checks: {glc_001: {...}, glc_002: {...}, ...}
judge: {default_provider: string, default_model: string, cross_provider: bool}Source: src/sanna/spec/receipt.schema.json
{
"spec_version": "1.5", // REQUIRED (current); verifier accepts "1.4", "1.3", "1.0" etc.
"tool_version": "1.5.0", // REQUIRED
"checks_version": "10", // REQUIRED (current); verifier dispatches by cv value
"tool_name": "sanna", // REQUIRED at cv>=9 (v1.4+)
"receipt_id": "uuid-v4", // REQUIRED
"receipt_fingerprint": "hex16", // REQUIRED — 16-char human-readable
"full_fingerprint": "hex64", // REQUIRED — 64-char programmatic
"correlation_id": "string", // REQUIRED — with path prefix
"timestamp": "ISO 8601", // REQUIRED
"inputs": { // REQUIRED
"query": "string | null | RedactionMarker",
"context": "string | null | RedactionMarker"
},
"outputs": { // REQUIRED
"response": "string | null | RedactionMarker"
},
"context_hash": "hex64", // REQUIRED
"output_hash": "hex64", // REQUIRED
"checks": [{ // REQUIRED
"check_id": "^(C[1-5]|INV_.+|sanna\\..+)$",
"name": "string",
"passed": true|false,
"severity": "info|warning|critical|high|medium|low",
"evidence": "string | null",
"details": "string | null",
"triggered_by": "string | null",
"enforcement_level": "halt|warn|log|null",
"status": "NOT_CHECKED|ERRORED|FAILED|null",
"check_impl": "string | null",
"replayable": true|false|null
}],
"checks_passed": 0, // REQUIRED (integer >= 0)
"checks_failed": 0, // REQUIRED (integer >= 0)
"status": "PASS|WARN|FAIL|PARTIAL", // REQUIRED
// --- Optional fields ---
"evaluation_coverage": {
"total_invariants": 0,
"evaluated": 0,
"not_checked": 0,
"coverage_basis_points": 0 // 0-10000 (10000 = 100%)
},
"constitution_ref": {
"document_id": "string",
"policy_hash": "hex16|hex64",
"constitution_approval": {...} | {"status": "unapproved"} | null
},
"enforcement": {
"action": "halted|warned|allowed|escalated",
"reason": "string",
"failed_checks": ["string"],
"enforcement_mode": "halt|warn|log",
"timestamp": "ISO 8601"
},
"receipt_signature": {
"signature": "string",
"key_id": "hex64",
"signed_by": "string",
"signed_at": "ISO 8601",
"scheme": "receipt_sig_v1"
},
"authority_decisions": [{
"action": "string",
"params": {},
"decision": "halt|allow|escalate",
"reason": "string",
"boundary_type": "cannot_execute|must_escalate|can_execute|uncategorized",
"timestamp": "ISO 8601" // REQUIRED in each record
}],
"escalation_events": [{...}],
"source_trust_evaluations": [{...}],
"identity_verification": {
"total_claims": 0,
"verified": 0,
"failed": 0,
"unverified": 0,
"all_verified": true|false,
"claims": [...]
},
"input_hash": "hex64 | null", // Receipt Triad
"reasoning_hash": "hex64 | null",
"action_hash": "hex64 | null",
"assurance": "full|partial|null",
"redacted_fields": ["inputs.context"],
"extensions": {} // Reverse-domain-namespaced
}RedactionMarker: {"__redacted__": true, "original_hash": "hex64"}
See src/sanna/gateway/config.py and full reference in docs/gateway-config.md.
gateway:
transport: stdio # only "stdio" supported
constitution: <path> # REQUIRED
signing_key: <path> # REQUIRED
constitution_public_key: <path> # optional
require_constitution_sig: true # default: true
receipt_store: <path> # optional
escalation_timeout: 300 # seconds
max_pending_escalations: 100
circuit_breaker_cooldown: 60 # seconds
gateway_secret_path: <path> # HMAC key for tokens
escalation_persist_path: <path> # persistent escalation store
approval_requires_reason: false
approval_webhook_url: <url> # token delivery webhook
token_expiry_seconds: 900 # 15 minutes
token_delivery: [stderr] # stderr|file|log|webhook|callback
redaction:
enabled: false
mode: hash_only # hash_only|pattern_redact
fields: [arguments, result_text]
downstream:
- name: <alphanumeric+hyphens+underscores> # REQUIRED
command: <executable> # REQUIRED
args: [<string>] # default: []
env: # optional
KEY: "${ENV_VAR}" # interpolated from os.environ
timeout: 30 # seconds
default_policy: can_execute # can_execute|must_escalate|cannot_execute
optional: false
tools: # per-tool overrides
"tool-name":
policy: must_escalate
reason: "Human-readable explanation"Policy cascade: per-tool override > server default_policy > constitution authority boundaries.
| Constant | Location | Value | Purpose |
|---|---|---|---|
TOOL_VERSION |
receipt.py (from version.py) |
"1.5.0" |
Package version in receipts |
SPEC_VERSION |
receipt.py |
"1.5" |
Receipt spec version |
CHECKS_VERSION |
receipt.py |
"10" |
Check algorithm version (part of fingerprint); 21-field formula at cv=10 (v1.5), 20-field at cv=9 (v1.4 legacy) |
TOOL_NAME |
receipt.py |
"sanna" |
Canonical SDK identity constant; fingerprint field 17 at cv≥9 |
_SCHEMA_VERSION |
store.py |
1 |
SQLite schema version |
BUNDLE_FORMAT_VERSION |
bundle.py |
"1.0.0" |
Evidence bundle format |
RECEIPT_VERSION_2 |
gateway/receipt_v2.py |
"2.0" |
Receipt v2 identifier |
Fingerprint cv-dispatch ladder:
| cv | Fields | Version |
|---|---|---|
| >=10 | 21 | v1.5 (adds agent_identity_hash at position 21; SAN-370) |
| >=9 | 20 | v1.4 (adds tool_name_hash, agent_model_hash, agent_model_provider_hash, agent_model_version_hash at positions 17-20) |
| 8 | 16 | v1.3 (adds enforcement_surface_hash, invariants_scope_hash at positions 15-16) |
| 6-7 | 14 | v1.0/v1.1 (adds parent_receipts_hash, workflow_id_hash at positions 13-14) |
| <=5 | 12 | pre-v1.0 |
The verifier auto-detects formula via checks_version. Legacy receipts (cv<=5) use _verify_fingerprint_legacy(). constitution_approval is stripped before fingerprinting in all four emit sites.
| Constant | Location | Value |
|---|---|---|
MAX_QUERY_LIMIT |
mcp/server.py |
500 |
MAX_RECEIPT_JSON_SIZE |
mcp/server.py |
1 MB |
MAX_CONTEXT_SIZE |
mcp/server.py |
500 KB |
MAX_RESPONSE_SIZE |
mcp/server.py |
500 KB |
MAX_ACTION_SIZE |
mcp/server.py |
10 KB |
MAX_STORED_PAYLOAD_BYTES |
gateway/receipt_v2.py |
64 KB (env-configurable) |
MAX_BUNDLE_MEMBERS |
bundle.py |
10 |
MAX_BUNDLE_FILE_SIZE |
bundle.py |
10 MB |
ED25519_SIGNATURE_LENGTH |
utils/crypto_validation.py |
64 bytes |
| Domain | Values | Used In |
|---|---|---|
| Receipt status | PASS, WARN, FAIL, PARTIAL |
SannaReceipt.status |
| Enforcement action | halted, warned, allowed, escalated |
Enforcement.action |
| Enforcement mode | halt, warn, log |
Invariant.enforcement |
| Authority decision | halt, allow, escalate |
AuthorityDecision.decision |
| Boundary type | cannot_execute, must_escalate, can_execute, uncategorized |
AuthorityDecision.boundary_type |
| Check status | NOT_CHECKED, ERRORED, FAILED, null |
CheckResult.status |
| Trust tier | tier_1, tier_2, tier_3, untrusted, unclassified |
source_trust_evaluations |
| Identity status | verified, unverified, failed, expired, no_key |
IdentityVerificationResult.status |
| Approval status | approved, pending, revoked, unapproved |
ApprovalRecord.status |
| Signature scheme | constitution_sig_v1, receipt_sig_v1 |
Signature blocks |
| Escalation type | log, webhook, callback |
EscalationTargetConfig.type |
| Circuit state | CLOSED, OPEN, HALF_OPEN |
CircuitState enum |
| Judge verdict | pass, fail, error, heuristic |
JudgeVerdict enum |
| Assurance level | full, partial, none |
Receipt assurance field |
| Redaction mode | hash_only, pattern_redact |
RedactionConfig.mode |
| Constant | Location | Value | Meaning |
|---|---|---|---|
_NON_EVALUATED |
middleware.py, verify.py, drift.py |
("NOT_CHECKED", "ERRORED") |
Excluded from pass/fail counts |
_STOP_WORDS |
enforcement/authority.py |
frozenset (a, an, the, or, ...) | Excluded from authority matching |
EMPTY_HASH |
hashing.py |
SHA-256 of b"" |
Sentinel for empty content |
Top-level exports (from sanna import ...):
from sanna import (
__version__, # "1.5.0"
sanna_observe, # Decorator for runtime enforcement
SannaResult, # Output + receipt wrapper
SannaHaltError, # Exception on enforcement halt
generate_receipt, # Generate receipt from trace data
SannaReceipt, # Receipt dataclass
verify_receipt, # Offline verification
VerificationResult, # Verification outcome
ReceiptStore, # SQLite persistence
DriftAnalyzer, # Governance drift analytics
# v1.0.0+ receipt sinks
ReceiptSink, SinkResult, FailurePolicy,
LocalSQLiteSink, NullSink, CloudHTTPSink, CompositeSink,
# v1.1.0+ interceptors
patch_subprocess, unpatch_subprocess,
patch_http, unpatch_http,
)Submodule imports (full public API):
# Constitution
from sanna.constitution import (
Constitution, load_constitution, save_constitution, parse_constitution,
sign_constitution, approve_constitution, scaffold_constitution,
compute_constitution_hash, compute_content_hash,
validate_constitution_data, validate_against_schema,
constitution_to_receipt_ref, constitution_to_signable_dict, constitution_to_dict,
verify_identity_claims,
AgentIdentity, IdentityClaim, IdentityVerificationResult, IdentityVerificationSummary,
Boundary, Invariant, Provenance, ConstitutionSignature,
HaltCondition, AuthorityBoundaries, EscalationRule, EscalationTargetConfig,
TrustedSources, TrustTiers, ApprovalRecord, ApprovalChain,
ReasoningConfig, SannaConstitutionError,
)
# Crypto
from sanna.crypto import (
generate_keypair, load_key_metadata,
load_private_key, load_public_key, compute_key_id,
sign_bytes, verify_signature, sanitize_for_signing,
sign_constitution_full, verify_constitution_full,
sign_receipt, verify_receipt_signature,
)
# Hashing
from sanna.hashing import hash_text, hash_obj, canonical_json_bytes, canonicalize_text
# Enforcement
from sanna.enforcement import (
CheckConfig, CustomInvariantRecord, configure_checks,
INVARIANT_CHECK_MAP, CHECK_REGISTRY,
AuthorityDecision, evaluate_authority,
EscalationTarget, EscalationResult, execute_escalation,
register_escalation_callback, clear_escalation_callbacks,
)
# Evaluators
from sanna.evaluators import (
register_invariant_evaluator, get_evaluator, list_evaluators, clear_evaluators,
)
from sanna.evaluators.llm import LLMJudge, enable_llm_checks, LLMEvaluationError
# Receipt
from sanna.receipt import (
CheckResult, SannaReceipt, Enforcement, ConstitutionProvenance, HaltEvent,
generate_receipt, select_final_answer, extract_context, extract_query,
extract_trace_data, TOOL_VERSION, SPEC_VERSION, CHECKS_VERSION,
)
# Middleware
from sanna.middleware import (
sanna_observe, SannaResult, SannaHaltError,
build_trace_data, generate_constitution_receipt,
)
# Verification
from sanna.verify import (
verify_receipt, VerificationResult, TriadVerification,
load_schema, verify_receipt_triad, verify_constitution_chain,
)
# Bundle
from sanna.bundle import create_bundle, verify_bundle, BundleVerificationResult, BundleCheck
# Store & Drift
from sanna.store import ReceiptStore
from sanna.drift import (
DriftAnalyzer, DriftReport, AgentDriftSummary, CheckDriftDetail,
calculate_slope, project_breach, format_drift_report,
export_drift_report, export_drift_report_to_file,
)
# Diffing
from sanna.constitution_diff import diff_constitutions, DiffResult, DiffEntry
# OTel (optional)
from sanna.exporters.otel_exporter import receipt_to_span, SannaOTelExporterAll registered in pyproject.toml [project.scripts]:
| Command | Entry Point | Description |
|---|---|---|
sanna |
cli:main_sanna |
Unified CLI dispatcher |
sanna-init |
init_constitution:main |
Interactive constitution generator |
sanna-keygen |
cli:main_keygen |
Generate Ed25519 keypair (--label) |
sanna-sign-constitution |
cli:main_sign_constitution |
Sign constitution |
sanna-verify-constitution |
cli:main_verify_constitution |
Verify constitution signature |
sanna-approve-constitution |
cli:approve_constitution_cmd |
Approve signed constitution |
sanna-diff |
cli:diff_cmd |
Diff two constitutions (text/JSON/markdown) |
sanna-generate |
cli:main_generate |
Generate receipt from trace-data JSON |
sanna-verify |
cli:main_verify |
Verify receipt (+signature +provenance) |
sanna-inspect |
cli:main_inspect |
Pretty-print receipt contents |
sanna-create-bundle |
cli:main_create_bundle |
Create evidence bundle |
sanna-verify-bundle |
cli:main_verify_bundle |
Verify evidence bundle (7-step) |
sanna-drift-report |
cli:main_drift_report |
Fleet governance drift report |
sanna-mcp |
mcp.__main__:main |
Start MCP server (stdio) |
sanna-gateway |
gateway:main |
Start MCP enforcement proxy |
sanna-demo |
cli:main_demo |
Self-contained governance demo |
sanna-check-config |
cli:main_check_config |
Validate gateway config (dry-run) |
MCP Server (sanna-mcp, 7 tools over stdio):
| Tool | Parameters |
|---|---|
sanna_verify_receipt |
receipt_json: str |
sanna_generate_receipt |
query, context, response, constitution_path? |
sanna_list_checks |
(none) |
sanna_evaluate_action |
action_name, action_params, constitution_path |
sanna_query_receipts |
db_path?, agent_id?, status?, since?, until?, halt_only?, limit?, analysis? |
sanna_check_constitution_approval |
constitution_path, author_public_key_path?, approver_public_key_path? |
sanna_verify_identity_claims |
constitution_path, provider_keys? |
Gateway Meta-Tools (sanna-gateway, 2 meta-tools + downstream passthrough):
| Tool | Purpose |
|---|---|
sanna_escalation_respond |
Approve/deny must_escalate tool calls |
sanna_gateway_status |
Gateway health and downstream status |
Downstream tools are prefixed: {server_name}_{original_tool_name}.
| Extension Point | Mechanism | Location |
|---|---|---|
| Custom evaluators | @register_invariant_evaluator(invariant_id) decorator |
evaluators/__init__.py |
| LLM evaluators | enable_llm_checks(api_key, model, checks) |
evaluators/llm.py |
| Escalation callbacks | register_escalation_callback(name, handler) |
enforcement/escalation.py |
| Receipt extensions | extensions dict field (reverse-domain-namespaced) |
Receipt JSON |
| Constitution extensions | identity.extensions dict (additionalProperties: allowed) |
Constitution YAML |
| OTel exporter | SannaOTelExporter as SpanExporter delegate |
exporters/otel_exporter.py |
| Constitution templates | YAML files in src/sanna/templates/ loaded via importlib.resources |
templates/ |
| Gateway downstream configs | YAML downstream: entries in gateway config |
gateway/config.py |
108 test files across tests/ (102 top-level) and tests/reasoning/ (6 files). See docs/state.md for the current count.
Naming conventions:
test_<subsystem>.py— e.g.,test_bundle.py,test_gateway_server.pytest_v<version>_<topic>.py— version-specific regressions, e.g.,test_v125_fixes.pytest_crit<N>_<topic>.py— critical security tests, e.g.,test_crit02_constitution_sig.pytest_sec<N>_<topic>.py— security category tests
86 of 87 top-level files use class-based organization (class TestFeature with def test_specific_case).
Root conftest sets SANNA_CONSTITUTION_PUBLIC_KEY env var pointing to pre-built test key in tests/.test_keys/.
71 total @pytest.fixture declarations across 31 test files. No subdirectory conftest files.
Common fixture patterns:
- Keypair fixtures —
generate_keypair(tmp_path / "keys") - Signed constitution fixtures — load → sign → save → return path
- Database fixtures —
ReceiptStore(tmp_path / "test.db");yield s; s.close() - Cleanup fixtures —
autouse=Truecallingclear_evaluators()to prevent registry leaks - Mock server scripts — embedded Python scripts for gateway MCP client tests
| Directory | Contents |
|---|---|
tests/vectors/ |
3 JSON vectors (canonicalization, constitution_signature, receipt_signature) + README |
tests/constitutions/ |
10 test constitution YAML files (all_halt, all_warn, with_authority, etc.) |
tests/.test_keys/ |
Pre-generated Ed25519 keypair (deterministic across runs) |
golden/receipts/ |
24 golden receipt JSON files covering all check outcomes |
| Subsystem | Test Files | Approx. Tests |
|---|---|---|
| Gateway (server, enforcement, escalation, config, hardening, migration) | 15 | 350+ |
| Constitution (parsing, signing, approval, diffing, identity, lifecycle) | 12 | 280+ |
| Receipt & Verification (golden, vectors, schema, fingerprint, triad) | 8 | 200+ |
| Security & Hardening (SSRF, redaction, webhooks, authority, versions) | 15 | 300+ |
| Middleware & Enforcement (decorator, checks, authority) | 7 | 180+ |
| Reasoning (deterministic checks, LLM coherence, pipeline, prompt security) | 6 | 110 |
| Evaluators (registry, LLM-as-Judge) | 4 | 120+ |
| Storage & Drift (SQLite, analytics, exports) | 4 | 100+ |
| Evidence Bundles (creation, verification, approval, key independence) | 3 | 80+ |
| CLI & Examples (interactive init, demos) | 4 | 50+ |
| Utilities (safe I/O, OTel, trust, extensions) | 7 | 100+ |
pytest.importorskip("mcp")— guards MCP/gateway tests when extra not installedpytest.importorskip("opentelemetry.sdk")— guards OTel testspytest.importorskip("httpx")— guards gateway HTTP tests@pytest.mark.asyncio— async test support (reasoning module)tests/generate_vectors.py-- deterministic vector generator (intentionally public seed0x01 * 32; not on any trust path; see tests/vectors/README.md)generate_golden.py— golden receipt generator at project root
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]| System | Integration | Module | Dependency |
|---|---|---|---|
| MCP protocol | stdio transport (server + client) | mcp/server.py, gateway/server.py, gateway/mcp_client.py |
mcp>=1.0 (optional) |
| Anthropic API | LLM-as-Judge evaluators | evaluators/llm.py, reasoning/llm_client.py |
stdlib urllib or httpx |
| OpenAI API | Cross-provider reasoning judge | reasoning/llm_client.py |
httpx (optional) |
| OpenTelemetry | Receipt → OTel span bridge | exporters/otel_exporter.py |
opentelemetry-api/sdk>=1.20.0 (optional) |
| Claude Desktop | MCP client config integration | gateway/migrate.py |
File system access |
| Downstream MCP servers | Notion, GitHub, filesystem, etc. | gateway/mcp_client.py |
mcp (child process spawning) |
There is no formal plugin/adapter interface. Integration patterns:
-
Custom evaluators — decorator-based registration via
@register_invariant_evaluator(invariant_id). Evaluator functions receive(context, output, constitution)and returnCheckResult. Exceptions produceERROREDstatus. -
Escalation callbacks —
register_escalation_callback(name, handler). Callbacks receive(event_details, timestamp). -
OTel exporter —
SannaOTelExporterwraps a delegateSpanExporterand filters forsanna.receipt.idattribute.receipt_to_span()creates spans from receipt metadata. -
Gateway downstreams — configured via YAML
downstream:entries. Any MCP-compatible server can be proxied.
Note: The Langfuse adapter was removed in v0.12.4. Context extraction logic was folded into extract_trace_data() in receipt.py.
| Variable | Default | Module | Purpose |
|---|---|---|---|
SANNA_CONSTITUTION_PUBLIC_KEY |
(none) | middleware.py, mcp/server.py, gateway/server.py |
Public key for verifying constitution signature |
SANNA_JUDGE_PROVIDER |
auto-detect | reasoning/judge_factory.py |
LLM provider: "anthropic" or "openai" |
SANNA_LLM_MODEL |
provider default | reasoning/llm_client.py |
Model name for LLM coherence evaluation |
ANTHROPIC_API_KEY |
(none) | evaluators/llm.py, reasoning/judge_factory.py |
Anthropic API key |
OPENAI_API_KEY |
(none) | reasoning/judge_factory.py |
OpenAI API key |
SANNA_GATEWAY_SECRET |
(none) | gateway/server.py |
HMAC-SHA256 secret for approval tokens (hex) |
SANNA_INSECURE_FILE_TOKENS |
"0" |
gateway/config.py |
Set "1" for file-based token delivery |
SANNA_ALLOW_INSECURE_WEBHOOK |
"0" |
gateway/config.py |
Set "1" for HTTP webhooks to localhost |
SANNA_ALLOW_TEMP_DB |
"0" |
store.py |
Set "1" for temporary in-memory SQLite |
SANNA_SKIP_DB_OWNERSHIP_CHECK |
"0" |
store.py |
Set "1" to skip SQLite ownership check |
SANNA_MAX_STORED_PAYLOAD_BYTES |
65536 |
gateway/receipt_v2.py |
Max bytes for receipt payloads |
| File | Format | Purpose |
|---|---|---|
constitution.yaml |
YAML | Agent governance policy (see §4.2) |
gateway.yaml |
YAML | Gateway proxy configuration (see §4.4) |
*.key / *.pub |
PEM | Ed25519 private/public keys |
*.meta.json |
JSON | Key metadata sidecar (key_id, label, created_at) |
receipts.db |
SQLite | Receipt persistence database |
pending_tokens.json |
JSON | Escalation token delivery file |
Gateway policy cascade:
- Per-tool override (exact match on unprefixed name from config
tools:map) - Server
default_policyon downstream entry - Constitution authority boundary matching (
evaluate_authority())
Constitution signing requirement:
require_constitution_sigin gateway config (default:true)require_constitution_sigparameter onsanna_observe()decorator (default:true)SANNA_CONSTITUTION_PUBLIC_KEYenv var for runtime verification
Base (pip install sanna):
jsonschema>=4.17pyyaml>=6.0cryptography>=41.0filelock>=3.0
Optional extras:
sanna[mcp]→mcp>=1.0sanna[otel]→opentelemetry-api>=1.20.0,opentelemetry-sdk>=1.20.0sanna[dev]→pytest>=7.0,jsonschema>=4.17,pyyaml>=6.0
Updated 2026-04-30 for sanna v1.5.0 (SAN-370). Generated from source.