kernite 0.1.5
Contract-first policy decision CLI for deterministic decision + reason + trace.
License: Apache-2.0
Requires Python: >=3.10

# Kernite

Kernite is an AI-operable policy engine for write-path enforcement.
Use it as a Python library or run the OSS decision server.

It is purpose-built for one job: return a reproducible and machine-operable decision contract before any mutation call such as `create`, `update`, `delete`, or `associate` is executed.

## Why Kernite

- Reason by Design: decision reasons are structured for programmatic remediation, not only human logs.
- Contract-First: `decision`, `reason_codes`, `reasons`, `trace_hash` are required integration surfaces.
- Reproducible tracing: canonical input and policy context produce deterministic `trace_hash` output.

## Design Philosophy
- Zero overhead: no runtime dependencies (`dependencies = []`).
- Zero DSL: enforce your policy without learning a new language.
- Zero bypass: every write-mutation gets reviewed, every time.

See `docs/design-philosophy.md` for more information on design principles.

## Compared with OPA/Cedar for AI Write Paths

| Topic | Kernite | OPA/Cedar |
| --- | --- | --- |
| Primary optimization | Machine remediation in write-path gating (`reason_codes` support fix-and-retry loops). | Human-authored policy ecosystems and policy-language tooling. |
| Decision contract | Required structured contract (`decision`, `reason_codes`, `reasons`, `trace_hash`). | Explanations exist, but the remediation contract is not standardized across integrations (codes/reasons/evidence vary by setup). |
| Reproducible evidence | Built-in deterministic evidence surface (`trace_hash`) plus conformance vectors for replay. | Strong policy engines; replay determinism depends on integration and policy/data pipeline discipline. |

While OPA/Cedar/others are strong options for centralized policy-language workflows, **Kernite is purpose-built for app-embedded, machine-operable, write-path enforcement with a stable response contract.**

## Non-goals

- A general-purpose policy language / DSL runtime.
- Proxying or routing traffic (Kernite returns decisions; your app performs the write).
- Replacing your domain logic. Kernite governs whether a write may proceed and why.


## Install and Start

```bash
uvx kernite start
```

If you get `invalid choice: 'start'`, your local CLI is an older release. Use one of:

```bash
uvx --from git+https://github.com/kerniteio/kernite kernite start
```

or:

```bash
uv tool install --upgrade kernite
kernite start
```

`kernite start` scaffolds a starter bundle at `./kernite/`.

If you want a different directory name:

```bash
uvx kernite start --dir kernite-demo
```

If the target directory already exists and is non-empty, use `--force` to overwrite.

Starter bundle files:

- `policy.json`
- `execute-request.denied.json`
- `execute-request.approved.json`
- `guard.py`
- `README.md`

`kernite scaffold` is an alias for the same command.

Run the OSS server:

```bash
uvx kernite serve
```

If port `8000` is already in use:

```bash
uvx kernite serve --host 127.0.0.1 --port 8010
```

Run the starter flow:

```bash
cd kernite

echo "=== denied ==="
curl -sS http://127.0.0.1:8000/v1/execute \
  -H 'content-type: application/json' \
  -H 'Idempotency-Key: readme-denied-001' \
  -d @execute-request.denied.json | python -m json.tool

echo "=== approved ==="
curl -sS http://127.0.0.1:8000/v1/execute \
  -H 'content-type: application/json' \
  -H 'Idempotency-Key: readme-approved-001' \
  -d @execute-request.approved.json | python -m json.tool
```

If you changed `--port`, update curl URLs accordingly (for example `http://127.0.0.1:8010`).

Apply this write-path rule:

- mutate only when `data.decision == "approved"`
- when denied, branch on `data.reason_codes` for remediation/retry
- persist `ctx_id`, `data.trace_hash`, `data.idempotency_key`

Server endpoints:

- `GET /health`
- `POST /execute`
- `POST /v1/execute`
- `POST /validate/execute`
- `POST /v1/validate/execute`

`/v1/execute` is the versioned stability surface; `/execute` maps to the latest stable surface (currently v1 in this OSS server).

`/execute` and `/v1/execute` are the public decision endpoints. Each request always goes through:

1. payload validation and normalization
2. deterministic policy evaluation

## Library Quick Start (Python)

```python
from kernite import evaluate_execute

request = {
    "workspace_id": "workspace-demo",
    "principal": {"type": "token", "id": "api:ops-bot"},
    "object_type": "document",
    "operation": "create",
    "payload": {"title": "Q1 Plan"},
}
result = evaluate_execute(request, idempotency_key="req-001")

decision = result["data"]["decision"]
trace_hash = result["data"]["trace_hash"]  # persist with your write/audit record
reason_codes = result["data"]["reason_codes"]
```

## Production Notes (OSS Reference Server)

- Authentication/authorization: the OSS reference 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: the OSS reference 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: the OSS reference server is minimal by design (startup log only, request access logs suppressed by default). Add structured logs/metrics in your service wrapper or edge layer, and persist `ctx_id`/`trace_hash` from responses for audit evidence.

## Quick Start: Execute

Request (governed scope with one policy, missing required `title`):

In v1, an `allow` policy is approved only if all its rules pass; any rule failure produces a `denied` decision with structured reasons.

```json
{
  "workspace_id": "workspace-demo",
  "principal": {
    "type": "token",
    "id": "api:ops-bot"
  },
  "object_type": "document",
  "operation": "create",
  "payload": {},
  "policy_context": {
    "governed": true,
    "selected_policies": [
      {
        "policy_key": "document_create_default",
        "policy_version": 1,
        "effect": "allow",
        "rules": [
          {
            "rule_key": "require_title",
            "rule_definition": {
              "type": "required_fields",
              "fields": ["title"]
            },
            "reason_code": "missing_required_fields",
            "reason_message": "title is required."
          }
        ]
      }
    ]
  }
}
```

Response shape:

```json
{
  "ctx_id": "ctx_...",
  "message": "Denied by governance policy.",
  "data": {
    "decision": "denied",
    "reason_codes": ["missing_required_fields"],
    "reasons": [
      {
        "code": "missing_required_fields",
        "message": "title is required.",
        "rule_key": "require_title",
        "field_path": "payload.title",
        "details": {
          "missing_fields": ["title"]
        }
      }
    ],
    "policy_selection_reason_code": "policy_selected_workspace_default",
    "policy": {
      "policy_key": "document_create_default",
      "policy_version": 1
    },
    "trace_hash": "sha256:...",
    "idempotency_key": "..."
  }
}
```

Happy-path response example (approved):

```json
{
  "ctx_id": "ctx_...",
  "message": "Approved by governance policy.",
  "data": {
    "decision": "approved",
    "reason_codes": [],
    "reasons": [],
    "policy_selection_reason_code": "policy_selected_workspace_default",
    "policy": {
      "policy_key": "document_create_default",
      "policy_version": 1
    },
    "trace_hash": "sha256:...",
    "idempotency_key": "..."
  }
}
```

Contract invariants (v1):

- Required response fields: `ctx_id`, `message`, `data`, `data.decision`, `data.reason_codes`, `data.reasons`, `data.policy_selection_reason_code`, `data.policy`, `data.trace_hash`, `data.idempotency_key`.
- `data.decision` enum values are only `approved` or `denied`.
- `message` is human-readable (best-effort); integrations should branch on `data.*` fields, not message text.
- `data.reason_codes` and `data.reasons` may be empty arrays when `data.decision` is `approved`.
- When `data.decision` is `denied`, at least one `data.reason_codes` entry is present.
- `data.trace_hash` is stable across v1.x for the same canonicalized input and policy context.
- Canonicalization rule: Kernite deterministically canonicalizes all hash-participating arrays (for example sorted `reason_codes` and canonical policy-match entries used for hashing). This makes order-only differences in `selected_policies`/`rules` not change `data.trace_hash`.

## Policy Context Model

`policy_context` is optional, but recommended for production integrations.
Kernite evaluates the policies you provide in `policy_context.selected_policies`; in OSS mode it does not fetch policies from storage.

Main fields:

- `governed` (bool): whether this request must be enforced as governed scope.
- `selected_policies` (array): policies selected by your resolver.
- `governed_scopes` (array): optional scope list (`object_type` + `operation`) to infer governed status.
- `policy_selection_reason_code` (string): explicit selection reason, if already known.

Default behavior:

- governed + no selected policy => `denied` with `no_matching_policy`
- not governed + no selected policy => `approved` with `out_of_scope_phase1`

## PARC Request Model

Kernite uses a Cedar-style PARC shape:

- principal
- action (`operation`)
- resource
- context

This keeps policy evaluation explicit and stable for relationship operations like `associate`.

See `docs/parc-model.md` for details and examples.

## Use Cases (AI and Non-AI)

- AI-assisted actions: gate tool calls and use `reason_codes` for automatic retry/remediation.
- Internal APIs: apply one deterministic write guard across UI/API/workers.
- SaaS multi-tenant systems: enforce tenant-scoped write decisions and persist evidence.

See `docs/use-cases.md` for details and examples.

## Compatibility and Conformance

- Contract policy: `docs/compatibility.md`
- Conformance vectors: `docs/conformance/v1/execute_vectors.json`
- Reason code semantics: `docs/conformance/v1/reason_codes_v1.json`

## Objective Performance Check (Python)

Kernite includes a dependency-free benchmark harness.

```bash
uv run python benchmarks/benchmark_execute.py --iterations 20000
```

This gives p50/p95 latency and throughput from your actual environment so language/runtime decisions are based on measured data.

Latest measured snapshot is tracked in `docs/performance.md`.