Metadata-Version: 2.4
Name: suvra
Version: 0.1.5
Summary: Policy-first execution guardrails for autonomous agents
License-Expression: MIT
Project-URL: Homepage, https://github.com/suvra-core/suvra
Project-URL: Repository, https://github.com/suvra-core/suvra
Project-URL: Documentation, https://github.com/suvra-core/suvra#readme
Project-URL: Bug Tracker, https://github.com/suvra-core/suvra/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.111.0
Requires-Dist: uvicorn>=0.30.0
Requires-Dist: pydantic>=2.7.0
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: httpx>=0.27.0
Requires-Dist: jinja2>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=8.2.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Dynamic: license-file

# Suvra

> Secure, policy-first execution guardrails for autonomous agents.

![Status](https://img.shields.io/badge/status-active-success)
![PyPI](https://img.shields.io/pypi/v/suvra)
![Python](https://img.shields.io/pypi/pyversions/suvra)

Suvra is a guardrail layer that sits between an agent and real-world side effects.
It evaluates declarative actions against `policy.yaml`, enforces allow/deny/approval decisions, executes deterministically, and logs an audit trail to SQLite.

This matters when you want AI automation with explicit control,
deterministic enforcement, explainable decisions, and rollback-ready operations.

Suvra turns probabilistic agent intent into policy-governed, auditable execution.

## Install

```bash
pip install suvra
```

Requires Python 3.9+.

## Core Guarantees

- Deny-by-default enforcement.
- Deterministic policy enforcement with no LLM in the enforcement path.
- Approval gating for sensitive actions.
- Workspace jail confinement with configurable root via `SUVRA_WORKSPACE_DIR`.
- Fail-closed guard-boundary actions (`shell.exec`, `email.delete`, `secrets.read`).
- SQLite audit trail.

## Architecture
Agent → Suvra Policy Engine → Executor → Audit Log

## Table of Contents

- [Features](#features)
- [Install](#install)
- [Core Guarantees](#core-guarantees)
- [Project Layout](#project-layout)
- [Quickstart](#quickstart)
  - [Embedded SDK (in-process)](#embedded-sdk-in-process)
  - [Remote SDK (HTTP)](#remote-sdk-http)
- [Examples](#examples)
  - [Embedded Guard usage](#embedded-guard-usage)
  - [Remote Guard usage](#remote-guard-usage)
  - [OpenClaw adapter example](#openclaw-adapter-example)
  - [guarded_tool decorator example](#guarded_tool-decorator-example)
- [API Usage](#api-usage)
- [CLI Usage](#cli-usage)
- [Dashboard](#dashboard)
- [Enforcement Modes](docs/enforcement_modes.md)
- [Feedback](#feedback)
- [License](#license)

## Features

- Policy-based enforcement with deny-by-default fallback.
- Decision modes: `allow`, `deny`, `needs_approval`.
- Deterministic action executors:
  - `fs.write_file`
  - `fs.delete_file`
  - `http.request` (GET only)
- Policy/simulation-only action types (recognized by policy + `/simulate`, execution fails closed):
  - `shell.exec`
  - `email.delete`
  - `secrets.read`
- Dry-run validation and execution flows.
- Approval workflow endpoints and rollback endpoint.
- Embedded SDK (`Guard(policy=...)`) and Remote SDK (`Guard(url=...)`).
- SQLite audit logging (`data/audit.db`) with durable rollback payload persistence.
- FastAPI service and a direct-library CLI.

## Project Layout

- `suvra/app/main.py` - FastAPI app
- `suvra/core/policy.py` - policy loading/evaluation
- `suvra/core/executors/` - action executors
- `suvra/core/audit.py` - SQLite audit log
- `suvra/core/service.py` - orchestration layer
- `suvra/sdk/guard.py` - SDK guard (embedded + remote)
- `suvra/sdk/decorators.py` - `guarded_tool` decorator
- `suvra/integrations/openclaw.py` - OpenClaw adapter
- `suvra/cli.py` - CLI tool
- `examples/` - sample action payloads
- `tests/` - pytest tests

## Quickstart

### Embedded SDK (in-process)

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
```

```python
from suvra import Guard

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

result = guard.execute(
    {
        "action_id": "quickstart-embedded",
        "type": "fs.write_file",
        "params": {
            "path": "workspace/quickstart_embedded.txt",
            "content": "hello from embedded mode"
        },
        "meta": {"actor": "quickstart"},
        "dry_run": False,
    }
)

print(result)
```

### Remote SDK (HTTP)

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
uvicorn suvra.app.main:app --reload
# or
suvra serve
```

```python
from suvra import Guard

guard = Guard(url="http://127.0.0.1:8000")

result = guard.validate(
    {
        "action_id": "quickstart-remote",
        "type": "fs.write_file",
        "params": {
            "path": "workspace/quickstart_remote.txt",
            "content": "hello from remote mode"
        },
        "meta": {"actor": "quickstart"},
        "dry_run": True,
    }
)

print(result)
```

Environment config:

- `SUVRA_POLICY_PATH` (default: `policy.yaml`)
- `SUVRA_DB_PATH` (default: `data/audit.db`)
- `SUVRA_WORKSPACE_DIR` (default: `workspace`)

### OpenClaw Quickstart: safest defaults

Suvra starts deny-by-default and only allows narrow actions from the built-in safe template on first run.
Writes must target the configured workspace root (default: `workspace/`), and deletes require approval.

```bash
docker run --rm -p 8000:8000 \
  -e SUVRA_ENV=dev \
  -e SUVRA_DB_PATH=/app/data/audit.db \
  -v "$(pwd)/workspace:/app/workspace" \
  -v "$(pwd)/data:/app/data" \
  suvra:latest
```

Use the configured workspace root (default `workspace/`) as the writable mount for OpenClaw tasks.

## Examples

### Embedded Guard usage

```python
from suvra import Guard

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

action = {
    "action_id": "sdk-embedded-1",
    "type": "fs.write_file",
    "params": {"path": "workspace/embedded.txt", "content": "hello"},
    "meta": {"actor": "sdk"},
    "dry_run": False,
}

result = guard.execute(action)
print(result)
```

### Remote Guard usage

```python
from suvra import Guard

guard = Guard(url="http://127.0.0.1:8000")

action = {
    "action_id": "sdk-remote-1",
    "type": "fs.write_file",
    "params": {"path": "workspace/remote.txt", "content": "hello"},
    "meta": {"actor": "sdk"},
    "dry_run": True,
}

result = guard.validate(action)
print(result)
```

`Guard` raises `ValueError` if both `policy` and `url` are provided.

### OpenClaw adapter example

```python
from suvra import Guard
from suvra.integrations.openclaw import SuvraExecutor

guard = Guard(policy="policy.yaml", db_path="data/audit.db")
executor = SuvraExecutor(guard)

execute_result = executor.execute(
    action_type="fs.write_file",
    params={"path": "workspace/demo/oc_adapter.txt", "content": "openclaw adapter"},
)

validate_result = executor.validate(
    action_type="fs.write_file",
    params={"path": "workspace/demo/oc_adapter.txt", "content": "openclaw adapter"},
)

print(execute_result)
print(validate_result)
```

### guarded_tool decorator example

```python
from pathlib import Path

from suvra import Guard, guarded_tool

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

@guarded_tool(guard, "fs.write_file")
def write_file(path: str, content: str) -> str:
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content)
    return str(target)

written = write_file(path="workspace/decorated.txt", content="decorator write")
print(written)
```

## API Usage

Run server:

```bash
uvicorn suvra.app.main:app --reload
# or
suvra serve
```

Docker:

```bash
docker build -t suvra:latest .
docker run --rm -p 8000:8000 \
  -e SUVRA_POLICY_PATH=/app/policy.yaml \
  -e SUVRA_DB_PATH=/app/data/audit.db \
  -v "$(pwd)/policy.yaml:/app/policy.yaml:ro" \
  -v "$(pwd)/data:/app/data" \
  suvra:latest
```

Docker Compose:

```bash
docker compose up --build
```

FastAPI endpoints:

- `GET /health`
- `POST /actions/validate`
- `POST /actions/execute`
- `POST /approvals/request`
- `POST /approvals/{approval_id}/approve`
- `POST /approvals/{approval_id}/deny`
- `GET /approvals/{approval_id}`
- `POST /rollback/{action_id}`
- `GET /audit`

Rollback is audit-backed: when an executor returns a rollback payload, Suvra persists it in `audit_events.rollback_payload` and treats the audit DB as the canonical rollback store. A fresh process can roll back prior actions as long as the same `data/audit.db` is available.

Canonical `ActionRequest` shape:

```json
{
  "action_id": "string",
  "type": "string",
  "params": {},
  "meta": {
    "actor": "string",
    "reason": "optional string",
    "approval_id": "optional string"
  },
  "dry_run": false
}
```

Guard-boundary-only action parameter shapes (recognized for policy and simulation, unsupported on execute):

- `shell.exec`:
  - `command` (string, required)
  - `args` (list[string], optional)
  - `cwd` (string, optional)
  - `dry_run` (bool, optional)
- `email.delete`:
  - `provider` (string, optional)
  - `message_id` (string, required)
  - `mailbox` (string, optional)
  - `reason` (string, optional)
  - `dry_run` (bool, optional)
- `secrets.read`:
  - `name` (string, required)
  - `purpose` (string, optional)
  - `dry_run` (bool, optional)

Backward compatibility is preserved for deprecated top-level `actor` and `approval_id`, but clients should migrate to `meta.actor` and `meta.approval_id`.

Validate (dry-run by design):

```bash
curl -s -X POST http://127.0.0.1:8000/actions/validate \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json
```

Execute action:

```bash
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json
```

Note: `shell.exec`, `email.delete`, and `secrets.read` are intentionally unsupported by execution.  
`POST /actions/execute` fails closed with `EXECUTION_ERROR` for those action types in all modes.

Execute in dry-run mode (no side effects):

```bash
jq '. + {dry_run: true}' examples/action_write_ok.json | \
  curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @-
```

Simulation-only policy examples (execution still unsupported):

```yaml
rules:
  - id: shell_git_status_only
    effect: allow
    type: shell.exec
    constraints:
      allow_commands: ["git status"]

  - id: email_delete_inbox_approval
    effect: needs_approval
    type: email.delete
    constraints:
      allow_providers: ["gmail"]
      allow_mailboxes: ["INBOX"]

  - id: secrets_openai_key_approval
    effect: needs_approval
    type: secrets.read
    constraints:
      allow_names: ["OPENAI_API_KEY"]
```

Approval flow example (default `policy.yaml` has `fs.delete_file` as `needs_approval`):

```bash
curl -s -X POST http://127.0.0.1:8000/actions/validate \
  -H 'content-type: application/json' \
  -d @examples/action_delete_file.json
```

Delete with approval + rollback demo (using default `policy.yaml`):

```bash
# 1) Write a file
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json

# 2) Execute delete without approval_id -> returns decision=needs_approval + approval_id
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_delete_file.json

# 3) Approve it
curl -s -X POST http://127.0.0.1:8000/approvals/<approval_id>/approve \
  -H 'content-type: application/json' \
  -d '{"decided_by":"admin","note":"approved"}'

# 4) Re-execute delete with meta.approval_id
jq --arg approval_id "<approval_id>" '.meta += {approval_id:$approval_id}' examples/action_delete_file.json | \
  curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @-

# 5) Roll back the delete by action_id
curl -s -X POST http://127.0.0.1:8000/rollback/delete-1 \
  -H 'content-type: application/json' \
  -d '{"dry_run":false}'

# 6) Verify restored file content
cat workspace/demo/hello.txt
```

The rollback call above works across service restarts because the rollback payload is loaded from SQLite, not process memory.

## CLI Usage
The CLI wraps the same EnforcementEngine used by the SDK and API.

Validate from JSON file:

```bash
python -m suvra.cli validate examples/action_write_ok.json
```

Execute from JSON file:

```bash
python -m suvra.cli execute examples/action_write_ok.json
```

Execute dry-run:

```bash
python -m suvra.cli execute examples/action_write_ok.json --dry-run
```

Run tests:

```bash
pytest
```

## Dashboard

Start the server:

```bash
suvra serve
```

Then open `http://127.0.0.1:8000/dashboard`.

## Feedback

Suvra is currently in soft beta.

If you're experimenting with it in OpenClaw or other agent frameworks and have feedback, ideas, or edge cases to report, feel free to reach out.

Early builder feedback is especially appreciated.

## Documentation Rule

Any change that affects:

- API endpoints
- Policy schema
- Enforcement logic
- Executors
- Enforcement modes
- Audit schema
- Dashboard routes
- SDK behavior
- Metrics

MUST update `docs/SUVRA_CONTEXT.md` in the same commit.

Commits modifying `core/` without updating `SUVRA_CONTEXT.md` are incomplete.

## License

MIT License. See the LICENSE file for details.
