Metadata-Version: 2.4
Name: pramiti-flight-recorder
Version: 0.3.0
Summary: Standalone AI agent action recorder with OCSF export and compliance reporting
Author-email: "Pramiti Labs, Inc." <hello@getpramiti.com>
License-Expression: MIT
Project-URL: Homepage, https://getpramiti.com
Project-URL: Repository, https://github.com/pramiti-labs/epistom
Keywords: ai,agent,audit,compliance,ocsf,epistom,pramiti
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: alembic>=1.13
Requires-Dist: cryptography>=42.0
Provides-Extra: postgres
Requires-Dist: psycopg2-binary; extra == "postgres"
Dynamic: license-file

# pramiti-flight-recorder

Standalone AI agent action recorder with Ed25519-signed, hash-chained records, OCSF Class 6003 export for SIEM integration, and compliance reporting for EU AI Act, SOC 2, and NIST AI RMF. Works with SQLite (local/dev) or PostgreSQL (production).

## Install

```bash
pip install pramiti-flight-recorder

# For PostgreSQL support:
pip install pramiti-flight-recorder[postgres]
```

## Quick Start

```python
from pramiti_flight_recorder import FlightRecorder

# Requires FR_SIGNING_PRIVATE_KEY (or FR_ALLOW_UNSIGNED=true for an ephemeral key).
# Set DATABASE_URL / EPISTOM_DATABASE_URL / FR_DATABASE_URL (no SQLite default for migrations — A5.2)
recorder = FlightRecorder()

# Or use PostgreSQL:
# recorder = FlightRecorder("postgresql://user:pass@localhost/mydb")

record_id = recorder.record(
    agent_id="agent-1",
    action="salesforce.update",
    verb="update",
    payload={"id": "C-123", "field": "email"},
    decision="allow",
    rationale="Customer requested email change",
)
```

## HTTP service authentication

The optional HTTP service (`pramiti.composition.flight_recorder_main`) is guarded by one shared bearer
token, `FR_API_KEY`. It may list several comma-separated keys so a key can be rotated without downtime.
There is no per-caller identity or scope: every holder of a key can read and write the whole store, so
deploy it behind an authenticating gateway when callers must be told apart. Production-like
environments refuse to serve without a key; outside production an unset key refuses too unless
`FR_OPEN=true`. `/health` (liveness) and `/readyz` (503 until the recorder is up) need no token.

## OCSF Export

Export records in OCSF Class 6003 (API Activity) format for Splunk, Sentinel, Datadog, or any SIEM:

```python
ocsf_events = recorder.export_ocsf()
# Also available: export_json(), export_csv()
```

## Compliance Reports

Generate compliance reports against three frameworks:

```python
report = recorder.compliance_report(
    framework="eu_ai_act",    # or "soc2" or "nist_ai_rmf"
    period_start="2026-01-01",
    period_end="2026-12-31",
)
print(f"Score: {report.score}, Status: {report.status}")
for check in report.checks:
    print(f"  [{check.id}] {'PASS' if check.passed else 'FAIL'}: {check.description}")
```

| Framework | Checks |
|-----------|--------|
| `eu_ai_act` | Article 12 (record-keeping: automatic recording, integrity/self-verification of signatures + hash chains, risk traceability), Article 13 (transparency/rationale), Article 14 (human oversight), Article 17 (risk management) |
| `soc2` | CC6.1 (logical access/constraints on denials), CC7.2 (monitoring coverage) |
| `nist_ai_rmf` | GOVERN 1.1 (signing key exists), MANAGE 2.4 (escalation workflow active) |

A zero-record period fails every check that depends on audit evidence (uniformly across frameworks): an empty period cannot demonstrate recording, transparency, oversight, monitoring, or active controls.

## Ed25519 Signing

All records are cryptographically signed with Ed25519. Set `FR_SIGNING_PRIVATE_KEY` to a stable private key; construction without it is refused unless `FR_ALLOW_UNSIGNED=true` (ephemeral per-process key, warned).

**What the signature and record hash cover** (signed-message format `v2`, current): `agent_id`, `action`, `verb`, `payload_hash`, `decision`, `constraints`, `rationale`, the record timestamp, and the previous record's hash. Editing any of these after the fact breaks hash recomputation and signature verification.

**Legacy records** (written by versions before 0.3.0, signed-message format `v1`) did **not** cover `constraints` and `rationale` — those two fields on legacy records are *not* tamper-evident. Legacy records still verify: the verifier detects each record's format from its hash (the format tag is committed inside the signed message, so a v2 record can never be downgraded to v1), and every verification surface (`pramiti-fr verify`, compliance reports) reports the count of legacy-format records together with this caveat.

**Key rotation**: changing `FR_SIGNING_PRIVATE_KEY` registers the new public key under the next key id (`v2`, `v3`, …) in `fr_signing_keys` — prior key rows are preserved, never overwritten. Each record stores the `signing_key_id` it was signed under, and verification resolves the key per record, so pre-rotation records remain verifiable. With `FR_ALLOW_UNSIGNED=true` and no `FR_SIGNING_PRIVATE_KEY`, every process start mints a fresh ephemeral key (a new key id per start, and no further records can be signed under it after the process exits). That start is refused by default.

Signing failure fails **closed** by default: `record()` raises and nothing is committed, so the store never silently contains unsigned records. Callers that prefer availability over the all-records-signed guarantee can opt in explicitly:

```python
fr = FlightRecorder(allow_unsigned=True)  # on signing failure: commit UNSIGNED
                                          # (signature_algorithm="none") with a
                                          # loud warning; such records fail
                                          # auditor verification later
```

Records are hash-chained: each record stores the SHA-256 hash of the previous record for the same agent, forming a tamper-evident chain.

```python
# Verify a record's signature
is_valid = recorder.verify(record_id)
```

## API

| Export | Description |
|--------|-------------|
| `FlightRecorder` | Core recorder. Methods: `record()`, `list()`, `get()`, `verify()`, `export_json()`, `export_csv()`, `export_ocsf()`, `compliance_report()`. |
| `ComplianceReport` | Dataclass: `framework`, `period_start`, `period_end`, `score`, `status`, `checks`. |
| `ComplianceCheck` | Dataclass: `id`, `description`, `passed`, `value`, `threshold`, `detail`. |
| `FrRecord` | SQLAlchemy model for action records (table: `fr_records`). |
| `FrSigningKey` | SQLAlchemy model for Ed25519 signing keys (table: `fr_signing_keys`). |
| `FrComplianceReport` | SQLAlchemy model for persisted compliance reports (table: `fr_compliance_reports`). |

## License

MIT -- Pramiti Labs
