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

# Quickstart

> End-to-end flow on a real FastAPI project from schema scan to enforce mode.

This quickstart uses a real FastAPI codebase:

* `https://github.com/fastapi/full-stack-fastapi-template`

Goal:

1. Scan live OpenAPI.
2. Generate initial policy artifacts.
3. Wire policy into route code and write decision logs.
4. Switch from `observe` to `enforce` mode.

## 1) Boot the FastAPI template

```bash theme={null}
git clone https://github.com/fastapi/full-stack-fastapi-template.git
cd full-stack-fastapi-template

# start only Postgres from compose
docker compose up -d db

# backend local env
cd backend
uv sync
source .venv/bin/activate
pip install kernite

# run migrations + initial data
bash scripts/prestart.sh

# start API in observe mode
KERNITE_MODE=observe fastapi dev app/main.py --host 127.0.0.1 --port 8000
```

Keep this terminal running. Use a second terminal for the steps below.

## 2) Pull OpenAPI and generate policy artifacts

From repo root (`full-stack-fastapi-template`):

```bash theme={null}
mkdir -p backend/.kernite
curl -sS http://127.0.0.1:8000/api/v1/openapi.json -o backend/.kernite/openapi.json

uvx kernite policy generate \
  --schema backend/.kernite/openapi.json \
  --out-dir backend/.kernite
```

Generated files:

* `backend/.kernite/policy-bundle.generated.json`
* `backend/.kernite/policy-map.generated.json`
* `backend/.kernite/policy-generation-report.json`

Coverage check (expected to fail strict initially because source OpenAPI has no `x-kernite` fields yet):

```bash theme={null}
uvx kernite check \
  --schema backend/.kernite/openapi.json \
  --report-out backend/.kernite/kernite-check.strict.json || true

jq '.summary' backend/.kernite/kernite-check.strict.json
```

## 3) Add one demo deny rule to generated policy

This makes mode switching obvious for one real route: `POST /api/v1/users/signup`.

```bash theme={null}
POLICY_KEY=$(jq -r '.operations[] | select(.path=="/api/v1/users/signup" and .method=="post") | .policy_key' backend/.kernite/policy-map.generated.json)

jq --arg policy_key "$POLICY_KEY" '
  (.policies[] | select(.policy_key == $policy_key) | .rules) |=
    (if any(.[]; .rule_key == "demo_password_allowlist") then
       .
     else
       . + [
         {
           "rule_key": "demo_password_allowlist",
           "rule_definition": {
             "type": "allowed_values",
             "field": "password",
             "values": ["demo-pass-123"]
           },
           "reason_code": "user_signup_password_not_allowlisted",
           "reason_message": "Demo guard: password must equal demo-pass-123."
         }
       ]
     end)
' backend/.kernite/policy-bundle.generated.json > backend/.kernite/policy-bundle.generated.next.json

mv backend/.kernite/policy-bundle.generated.next.json backend/.kernite/policy-bundle.generated.json
```

## 4) Wire policy into route code (observe mode)

Create `backend/app/core/kernite_guard.py`:

```python theme={null}
from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any

from kernite import JsonlDecisionSink, evaluate_execute_controlled

POLICY_DIR = Path(__file__).resolve().parents[2] / ".kernite"
BUNDLE = json.loads((POLICY_DIR / "policy-bundle.generated.json").read_text(encoding="utf-8"))
MAPPING = json.loads((POLICY_DIR / "policy-map.generated.json").read_text(encoding="utf-8"))

POLICIES = {policy["policy_key"]: policy for policy in BUNDLE.get("policies", [])}
OPERATIONS = {
    (item["method"].upper(), item["path"]): item
    for item in MAPPING.get("operations", [])
}
SINK = JsonlDecisionSink(POLICY_DIR / "decision-events.jsonl")


def evaluate_route(
    *,
    method: str,
    path: str,
    principal_id: str,
    payload: dict[str, Any],
) -> dict[str, Any]:
    operation = OPERATIONS.get((method.upper(), path))
    if operation is None:
        return {
            "allow_write": True,
            "mode": "skip",
            "decision_raw": "skipped",
            "decision_effective": "approved",
            "governance": None,
            "sink_status": "skipped",
        }

    policy_key = operation.get("policy_key")
    selected_policies = [POLICIES[policy_key]] if policy_key in POLICIES else []

    request_body = {
        "workspace_id": "workspace-demo",
        "principal": {"type": "token", "id": principal_id},
        "object_type": operation["object_type"],
        "operation": operation["operation"],
        "payload": payload,
        "policy_context": {
            "governed": bool(operation.get("governed", False)),
            "selected_policies": selected_policies,
            "governed_scopes": BUNDLE.get("governed_scopes", []),
            "policy_selection_reason_code": "policy_selected_workspace_default",
        },
    }

    return evaluate_execute_controlled(
        request_body,
        mode=os.getenv("KERNITE_MODE", "observe"),
        sink=SINK,
        sink_failure_policy="fail_open",
    )
```

Patch `backend/app/api/routes/users.py` inside `register_user(...)`:

```python theme={null}
from app.core.kernite_guard import evaluate_route

# inside register_user(...)
governance = evaluate_route(
    method="POST",
    path="/api/v1/users/signup",
    principal_id=f"public:{user_in.email}",
    payload=user_in.model_dump(exclude_none=True),
)
if not governance["allow_write"]:
    raise HTTPException(
        status_code=403,
        detail={
            "reason_codes": governance["governance"]["data"]["reason_codes"],
            "trace_hash": governance["governance"]["data"]["trace_hash"],
        },
    )
```

The backend dev server reloads automatically after file changes.

## 5) Observe mode demo

Run a signup request that should be denied by policy but still allowed in observe mode:

```bash theme={null}
curl -sS -X POST http://127.0.0.1:8000/api/v1/users/signup \
  -H 'content-type: application/json' \
  -d '{"email":"kernite-observe-1@example.com","password":"not-allowed","full_name":"Observe Demo"}' | jq .
```

Inspect decision event:

```bash theme={null}
tail -n 1 backend/.kernite/decision-events.jsonl | jq '{mode, decision_raw, decision_effective, allow_write, reason_codes, sink_status}'
```

Expected:

* `mode = "observe"`
* `decision_raw = "denied"`
* `decision_effective = "approved"`
* `allow_write = true`

## 6) Enforce mode demo

Restart backend in enforce mode:

```bash theme={null}
cd backend
source .venv/bin/activate
KERNITE_MODE=enforce fastapi dev app/main.py --host 127.0.0.1 --port 8000
```

Denied request (now blocked):

```bash theme={null}
curl -sS -X POST http://127.0.0.1:8000/api/v1/users/signup \
  -H 'content-type: application/json' \
  -d '{"email":"kernite-enforce-deny-1@example.com","password":"not-allowed","full_name":"Enforce Deny"}' | jq .
```

Approved request:

```bash theme={null}
curl -sS -X POST http://127.0.0.1:8000/api/v1/users/signup \
  -H 'content-type: application/json' \
  -d '{"email":"kernite-enforce-ok-1@example.com","password":"demo-pass-123","full_name":"Enforce OK"}' | jq .
```

You now have one complete real-project flow:

* OpenAPI scan
* initial policy generation
* in-code decision logging
* observe to enforce switch with the same integration contract
