> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kernite.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Persistence and Logging

> Governance evidence sinks for approved and denied decisions.

# Persistence

Kernite core evaluation is dependency-free and stateless by default. It does not write to your database automatically.

Kernite can emit decision events when you use controlled evaluation with a configured sink (`jsonl`, `csv`, `sqlite`).
In production, still treat evidence persistence as an app-level responsibility: capture the decision contract from `evaluate_execute(...)`, `evaluate_execute_controlled(...)`, or `POST /execute` and write it to a durable sink (DB, object storage, log pipeline, etc.).

## Why persist governance evidence

Persisting governance evidence enables:

* Auditability: reproduce what was evaluated and why for any write attempt.
* Security posture reporting: quantify what was blocked (deny events) and why.
* Operational debugging: reason codes and trace hashes reduce time-to-resolution.
* Analytics: top deny reasons, noisy principals, hot operations, and policy regressions.

## Production Notes

* Authentication/authorization: Kernite server does not include built-in authn/authz. Run it behind your trusted boundary (for example mTLS, JWT verification, internal network policy, or API gateway auth).
* Request size/timeouts: Kernite server does not define built-in max body size or per-request timeout controls. Enforce size limits and timeouts at ingress/runtime (reverse proxy, gateway, or process supervisor).
* Logging/metrics: Kernite server is minimal by design (startup log only, request access logs suppressed by default). For app-embedded flows, you can also enable Kernite decision sinks (`KERNITE_SINK`, `KERNITE_SINK_PATH`) to persist event records, then forward/aggregate them in your telemetry stack.

## The decision contract (what to store)

### Persistence best practices

Do not store only approvals or . Store:

* approved decisions (writes that were allowed)
* denied decisions (writes that were blocked)
* application outcomes for request errors (for example invalid request or transport failure)

Most security value comes from denied events, meaning what your guard actually prevented.

### Recommended minimal record

Store these fields in your sink:

* `created_at` (server timestamp)
* `workspace_id`
* `principal_id`
* `object_type`
* `operation`
* `decision` (`approved` | `denied`)
* `outcome` (`approved` | `denied` | `error`) optional, app-level
* `reason_codes[]`
* `ctx_id`
* `trace_hash`
* `idempotency_key`
* `policy_version` (if available)
* `latency_ms` (if measured)

Optionally store:

* `request_json` (redacted/sanitized)
* `response_json` (full decision response; may be large)

Tip: if you only store one fat blob, store full `response_json`. If you need analytics, store indexable columns plus optional JSON.

## Transaction semantics

A robust write path is:

1. Evaluate: `evaluate_execute(...)` (or `POST /execute`)
2. Persist governance evidence (approved/denied/error outcome)
3. If `decision == "approved"`, perform the actual mutation
4. Return `{ok, data, governance}`

### Strong pattern: same transaction for evidence and mutation

If your app uses a DB transaction, write both governance evidence and domain mutation in the same transaction. This prevents missing evidence for successful writes.

## Failure policy: fail-closed vs fail-open

Decide what happens if your sink is unavailable.

### Fail-closed (strict audit/compliance)

If sink write fails, deny the operation. This guarantees evidence exists for any allowed mutation.

### Fail-open (availability first) + outbox

If availability matters more, allow mutation even if sink write fails, then retry evidence persistence via outbox table or queue.

## Patterns by sink type

### Database sink (recommended for analytics)

Pros:

* easy querying (top deny reasons, per-principal rates, per-operation breakdown)
* optional joins with domain data

Cons:

* schema and retention planning required

Suggested schema:

* index `workspace_id`, `principal_id`, `decision`, `created_at`
* unique key `(workspace_id, idempotency_key)` for retry dedupe

### Object storage sink (S3/GCS/Azure Blob)

Pros:

* cheap and durable
* good for long-term retention

Cons:

* querying needs secondary system (Athena/BigQuery/etc.) or ETL

Recommended format:

* JSONL partitioned by date, for example `s3://bucket/kernite/year=YYYY/month=MM/day=DD/*.jsonl`

### Log pipeline sink (OpenTelemetry/Datadog/Cloud Logging)

Pros:

* immediate dashboards and alerts
* easy anomaly detection (deny spikes)

Cons:

* retention and full-fidelity JSON may be expensive

Tip:

* emit metric counters for `decision` and `reason_codes`
* emit structured logs including `trace_hash` and `ctx_id`

## Redaction and sensitive data

Governance evidence may contain sensitive data (PII, secrets, tokens). If you store `request_json` or full `response_json`, apply redaction.

* remove or hash emails, phone numbers, addresses
* remove tokens, credentials, and secrets
* prefer IDs/references over raw payload values

Safe default:

* store indexable fields plus `reason_codes`
* store a redacted subset of request/response JSON

## Example: minimal persistence in an application write path

Replace `persist_evidence(...)` with your sink implementation.

```python theme={null}
from kernite import evaluate_execute

def guard_and_write(mutate_fn, req: dict, idempotency_key: str):
    result = evaluate_execute(req, idempotency_key=idempotency_key)
    d = result["data"]

    persist_evidence(
        workspace_id=req["workspace_id"],
        principal_id=req["principal"]["id"],
        object_type=req["object_type"],
        operation=req["operation"],
        decision=d["decision"],
        reason_codes=d.get("reason_codes", []),
        ctx_id=result["ctx_id"],
        trace_hash=d["trace_hash"],
        idempotency_key=d["idempotency_key"],
        response_json=result,
    )

    if d["decision"] != "approved":
        return {"ok": False, "governance": result}

    out = mutate_fn()
    return {"ok": True, "data": out, "governance": result}
```

## Framework notes

Kernite is framework-agnostic. This pattern works in FastAPI/Starlette, Django, Flask, internal services, and batch jobs.

Choose sink and failure policy based on compliance and availability requirements.
