Skip to main content

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. 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

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.
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.