Metadata-Version: 2.4
Name: fingate
Version: 0.2.1
Summary: AI Agent Payment Trust Layer - Deterministic authorization for agent payments
Home-page: https://github.com/arnav7897/GateKeeper---AI-Agent-Trust-Layer
Author: GateKeeper Team
License: MIT
Project-URL: Homepage, https://github.com/arnav7897/GateKeeper---AI-Agent-Trust-Layer
Project-URL: Documentation, https://github.com/arnav7897/GateKeeper---AI-Agent-Trust-Layer/blob/main/README.md
Project-URL: Repository, https://github.com/arnav7897/GateKeeper---AI-Agent-Trust-Layer
Project-URL: Bug Tracker, https://github.com/arnav7897/GateKeeper---AI-Agent-Trust-Layer/issues
Keywords: ai,agents,payments,security,authorization,trust-layer
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: click>=8.0.0
Provides-Extra: razorpay
Requires-Dist: razorpay>=2.0.0; extra == "razorpay"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.5.0; extra == "anthropic"
Provides-Extra: gemini
Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python

# FinGate — AI Agent Payment Trust Layer

[![PyPI Version](https://img.shields.io/pypi/v/fingate.svg)](https://pypi.org/project/fingate/)
[![Python Versions](https://img.shields.io/pypi/pyversions/fingate.svg)](https://pypi.org/project/fingate/)
[![License](https://img.shields.io/pypi/l/fingate.svg)](https://pypi.org/project/fingate/)
[![Status](https://img.shields.io/pypi/status/fingate.svg)](https://pypi.org/project/fingate/)

**FinGate** is a deterministic, explainable authorization layer for AI agent payment
requests. It evaluates every transaction *before* money moves — policy enforcement,
explainable risk scoring, and optional LLM intent analysis produce `APPROVE` /
`REVIEW` / `BLOCK` decisions with a full audit trail.

The payment gateway is contacted **only after** FinGate returns `APPROVE`.
The LLM can make FinGate smarter; deterministic rules make it trustworthy — the LLM
never authorizes anything.

---

## Installation

```bash
pip install fingate
```

**Zero dependencies required** for core functionality. Optional extras:

```bash
pip install fingate[razorpay]     # Razorpay payment gateway
pip install fingate[anthropic]    # Anthropic intent analyzer
pip install fingate[gemini]       # Google Gemini intent analyzer
pip install fingate[dev]          # Development tools (pytest, black, mypy)
```

---

## Quick Start

### 1. Initialize a policy

```bash
gatekeeper init
# Creates:
#   gatekeeper.policy.yaml   — policy configuration
#   .env.example            — environment variables
#   POLICY.md               — human-readable policy documentation
```

### 2. Protect any payment function

```python
from gatekeeper import protect
from gatekeeper.adapters import MockAdapter
from gatekeeper.models import PaymentIntent, Recipient, RecipientType

# Your original payment function
def execute_payment(payment_intent, adapter, **kwargs):
    return adapter.create_payment_link(
        amount=payment_intent.amount,
        currency=payment_intent.currency,
        description=payment_intent.purpose,
    )

# Wrap it with FinGate — one line makes it safe
safe_payment = protect(
    execute_payment,
    policy_config={"agent": {"name": "my-agent"}, "limits": {...}, ...},
    gateway_adapter=MockAdapter(),
)

# Use the protected function
intent = PaymentIntent(
    agent_id="my-agent",
    amount=8000,
    recipient=Recipient(type=RecipientType.MERCHANT, id="github_001", name="GitHub"),
    category="developer_tools",
    purpose="GitHub Team seat",
)

result = safe_payment(intent)

if result.decision.is_approve():
    print(f"Payment approved — link: {result.payment_link}")
elif result.decision.is_review():
    print(f"Review required: {result.decision.reason}")
else:
    print(f"Blocked: {result.decision.reason}")
# Payment link is created ONLY on APPROVE — nothing runs otherwise
```

### 3. Run the demo

```bash
python demo_agent/demo_agent.py all
# Four scenarios: normal approve, policy violation, intent mismatch, amount anomaly
```

---

## Why FinGate?

AI agents can be compromised or buggy. FinGate acts as a fail-closed safety net:

```
AI agent asks to spend money
        ↓
FinGate verifies authority + intent + risk
        ↓
Decision is explainable (rule-by-rule, feature-by-feature)
        ↓
Only an APPROVED transaction reaches Razorpay/Stripe
        ↓
The payment outcome is audited
```

| Gap in existing infra | How FinGate closes it |
|---|---|
| No authority verification | Per-agent policy as code: amount caps, daily limits, category allow/block |
| No semantic validation | Optional LLM intent analyzer detects prompt-injection-style deviations |
| No anomaly detection | Explainable risk score 0–100 from five weighted features |
| No explainability | Every decision stores rule PASS/FAIL, risk contributions, and timestamps |

---

## API Reference

All public APIs are importable from the `gatekeeper` package:

```python
from gatekeeper import (
    protect,              # Primary middleware wrapper
    evaluate_payment_intent,  # Dry-run evaluation
    create_protected_payment_function,  # Factory
    ProtectResult,        # Result dataclass
    PaymentIntent,        # Payment request model
    Decision,             # Authorization decision model
    AgentPolicy,         # Policy configuration model
    MerchantProfile,      # Merchant risk data
    Recipient,           # Payment recipient
    RecipientType,        # Recipient type enum
    DecisionAction,      # APPROVE / REVIEW / BLOCK enum
    GateKeeperMCPTool,   # MCP tool wrapper
    build_claude_tool_spec,  # Anthropic tool spec helper
    PaymentGatewayAdapter,  # Gateway interface
    MockAdapter,          # Offline testing adapter
    WebhookEvent,         # Webhook event model
    PaymentStatus,        # Payment status enum
)
```

---

### `protect(payment_fn, policy_config, gateway_adapter=None, transaction_history=None)`

**The primary API.** Wrap any payment function with one call.

```python
safe_payment = protect(
    payment_fn,              # Callable — your original payment function
    policy_config,            # dict | None — agent policy (or None to use MCP tool)
    gateway_adapter,          # PaymentGatewayAdapter | None — defaults to MockAdapter
    transaction_history,       # list[dict] | None — for daily-limit checks
)
result = safe_payment(payment_intent, **kwargs)
```

**Arguments:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `payment_fn` | `Callable` | ✅ | Original payment function to protect. Receives `(payment_intent, adapter, **kwargs)`. |
| `policy_config` | `dict \| None` | ✅ | Agent policy dict. Loaded via `AgentPolicy.from_dict()`. |
| `gateway_adapter` | `PaymentGatewayAdapter \| None` | No | Payment gateway. Defaults to `MockAdapter()`. |
| `transaction_history` | `list[dict] \| None` | No | Prior transactions for daily-limit and velocity checks. |

**Returns:** A wrapped callable that accepts a `PaymentIntent` and returns a `ProtectResult`.

**Raises:** Nothing — all errors are captured in `ProtectResult.error` (fail-closed).

---

### `ProtectResult`

Dataclass returned by every protected payment call.

```python
@dataclass
class ProtectResult:
    decision: Decision          # The authorization decision
    payment_link: str | None   # Payment link URL (APPROVE only)
    payment_id: str | None     # Payment ID (APPROVE only)
    error: str | None          # Error message if fail-closed
    timestamp: str             # ISO 8601 timestamp
```

**Methods:**

| Method | Returns | Description |
|---|---|---|
| `result.decision.is_approve()` | `bool` | True if decision is `APPROVE` |
| `result.decision.is_review()` | `bool` | True if decision is `REVIEW` |
| `result.decision.is_block()` | `bool` | True if decision is `BLOCK` |
| `result.was_successful()` | `bool` | True if APPROVED and link was created |
| `result.to_dict()` | `dict` | Serializable representation |

---

### `evaluate_payment_intent(payment_intent, policy_config, transaction_history=None)`

Dry-run evaluation — returns a `Decision` without executing any payment.

```python
decision = evaluate_payment_intent(payment_intent, my_policy, history)
if decision.is_approve():
    print("Would approve")
```

---

### `create_protected_payment_function(payment_fn, policy_file=None, gateway_type="mock", **gateway_config)`

Factory function for common setup patterns.

```python
safe_pay = create_protected_payment_function(
    my_payment_fn,
    policy_file="./gatekeeper.policy.yaml",
    gateway_type="razorpay",
    key_id="rzp_test_...",
    key_secret="...",
)
```

---

### `PaymentIntent`

Canonical payment request object. Construct with the factory or directly:

```python
from gatekeeper.models import PaymentIntent, Recipient, RecipientType

intent = PaymentIntent(
    agent_id="procurement-bot",    # Required
    amount=8000,                   # Required — in paise (smallest currency unit)
    recipient=Recipient(           # Required
        type=RecipientType.MERCHANT,
        id="github_001",
        name="GitHub",
    ),
    category="developer_tools",    # Required
    purpose="GitHub Team seat",    # Optional description
    metadata={                     # Optional free-form dict
        "declared_intent": "GitHub Team seat for new engineer",
        "actual_description": "GitHub Team Plan - annual subscription",
        "country": "IN",
    },
)
intent.validate()  # Raises ValueError if invalid
```

**Constructor arguments:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `agent_id` | `str` | — | Agent making the payment |
| `amount` | `int` | `0` | Amount in smallest currency unit (e.g., paise for INR) |
| `currency` | `str` | `"INR"` | 3-letter ISO 4217 currency code |
| `recipient` | `Recipient` | `None` | Payment recipient |
| `category` | `str` | `""` | Transaction category |
| `purpose` | `str` | `""` | Human-readable description |
| `metadata` | `dict` | `{}` | Arbitrary key-value pairs |
| `action` | `str` | `"payment"` | Action type: `payment`, `refund`, `transfer` |

**Factory function:**

```python
intent = create_payment_intent(
    agent_id="my-agent",
    amount=8000,
    recipient_id="github_001",
    category="developer_tools",
    purpose="GitHub subscription",
)
```

---

### `Decision`

Authorization decision returned by the decision engine.

```python
@dataclass
class Decision:
    action: DecisionAction           # APPROVE | REVIEW | BLOCK
    reason: str                      # Human-readable explanation
    risk_score: int                  # 0–100
    policy_passed: bool              # All deterministic checks passed
    requires_review: bool            # Human approval required
    decision_metadata: dict          # Additional structured data
    timestamp: str | None
    transaction_id: str | None
```

**Decision thresholds:**

| Condition | Decision |
|---|---|
| Hard policy violation | `BLOCK` (always) |
| Evaluation error / missing safety data | `BLOCK` (fail-closed) |
| Risk score 0–29 | `APPROVE` |
| Risk score 30–69 | `REVIEW` |
| Risk score 70–100 | `BLOCK` |

---

### `AgentPolicy`

Agent policy configuration loaded from YAML or dict.

```python
from gatekeeper.models import AgentPolicy

# From dict (as returned by YAML config)
policy = AgentPolicy.from_dict({
    "agent": {"name": "procurement-bot", "owner": "engineering"},
    "limits": {"max_transaction_amount": 25000, "daily_limit": 100000},
    "categories": {"allowed": ["developer_tools"], "blocked": ["gift_cards"]},
})

# From YAML file
import yaml
with open("gatekeeper.policy.yaml") as f:
    policy = AgentPolicy.from_dict(yaml.safe_load(f))
```

---

### MCP Tool — `GateKeeperMCPTool`

Expose FinGate as a native MCP tool for Claude-style agents and any MCP runtime.

```python
from gatekeeper import GateKeeperMCPTool, build_claude_tool_spec

# Initialize from policy file
tool = GateKeeperMCPTool.from_policy_file("./gatekeeper.policy.yaml")

# Handle a tool call (plain dict in, dict out)
result = tool.handler({
    "amount": 8000,
    "category": "developer_tools",
    "declared_intent": "GitHub Team seat",
    "actual_description": "GitHub Team Plan - annual",
    "merchant_name": "GitHub",
})

# result["decision"] == "APPROVE" | "REVIEW" | "BLOCK"
# result["risk_score"]  == 13
# result["reason"]      == "..."
```

**For Anthropic Messages API:**

```python
import anthropic
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-5",
    system="Before ANY payment, call the evaluate_payment tool. Proceed only if the "
          "result's decision is exactly 'APPROVE'. Treat REVIEW and BLOCK as final.",
    tools=[build_claude_tool_spec()],  # Pass tool spec to the API
    messages=[...],
)
```

---

### Adapters

`PaymentGatewayAdapter` is the interface for payment providers. Three adapters are included:

| Adapter | Use case | Install |
|---|---|---|
| `MockAdapter` | Offline testing, demos | Core (no extra deps) |
| `RazorpayAdapter` | Razorpay Test Mode / production | `pip install fingate[razorpay]` |
| `StripeAdapter` | Stripe payments | `pip install fingate[stripe]` |

```python
from gatekeeper.adapters import MockAdapter, PaymentGatewayAdapter

# MockAdapter (default, offline)
adapter = MockAdapter()

# RazorpayAdapter
from gatekeeper.adapters import create_razorpay_adapter
adapter = create_razorpay_adapter(
    key_id="rzp_test_...",
    key_secret="...",
)

# StripeAdapter
from gatekeeper.adapters import create_stripe_adapter
adapter = create_stripe_adapter(api_key="sk_test_...")
```

---

## Policy Configuration

Define agent policies in YAML. Load via `gatekeeper init` or write manually:

```yaml
agent:
  name: procurement-bot
  owner: engineering-team
  version: "1.0.0"

limits:
  max_transaction_amount: 25000   # ₹250.00 in paise (smallest unit)
  daily_limit: 100000              # ₹1,000.00
  require_human_approval_above: 25000  # Human needed above ₹250

categories:
  allowed: [developer_tools, saas_subscriptions, cloud_services, infrastructure]
  blocked: [gift_cards, cryptocurrency, gambling, adult_content]

countries:
  allowed: [IN, US]
  blocked: []

merchants:
  allowlist_enabled: false
  allowed_merchants: []
  blocked_merchants: []

velocity:
  max_per_minute: 3
  max_per_hour: 20
  max_per_day: 100

gateway:
  provider: mock
  currency: INR

intent:
  provider: none      # Options: none, anthropic, gemini
  strictness: 0.7

risk:
  approve_threshold: 30
  review_threshold: 70
  weights:
    policy_violation: 0.4
    amount_anomaly: 0.2
    merchant_risk: 0.15
    velocity: 0.15
    intent_deviation: 0.1
```

**All amounts are in the smallest currency unit** (e.g., paise for INR, cents for USD).
₹250.00 = `25000` paise.

### Policy fields

| Field | Type | Description |
|---|---|---|
| `max_transaction_amount` | `int` | Maximum per-transaction limit in paise |
| `daily_limit` | `int` | Rolling 24h spending cap in paise |
| `require_human_approval_above` | `int` | REVIEW threshold (paise) |
| `allowed_categories` | `list[str]` | Permitted transaction categories |
| `blocked_categories` | `list[str]` | Hard-blocked categories |
| `allowed_countries` | `list[str]` | Permitted merchant countries (ISO 3166-1) |
| `allowed_merchants` | `list[str]` | Whitelisted merchant IDs |
| `blocked_merchants` | `list[str]` | Blacklisted merchant IDs |
| `velocity_max_per_minute` | `int` | Max transactions per minute |
| `velocity_max_per_hour` | `int` | Max transactions per hour |
| `velocity_max_per_day` | `int` | Max transactions per day |

---

## Environment Variables

**Core package needs no variables.** All defaults work offline.

| Variable | Adapter / Feature | Description |
|---|---|---|
| `RAZORPAY_KEY_ID` | `RazorpayAdapter` | Razorpay key ID |
| `RAZORPAY_KEY_SECRET` | `RazorpayAdapter` | Razorpay key secret |
| `RAZORPAY_WEBHOOK_SECRET` | Webhook verification | HMAC webhook secret |
| `RAZORPAY_TEST_MODE` | `RazorpayAdapter` | Use test mode (default: `true`) |
| `STRIPE_API_KEY` | `StripeAdapter` | Stripe API key |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhooks | Stripe webhook secret |
| `GEMINI_API_KEY` | Gemini intent analyzer | Google Gemini API key |
| `ANTHROPIC_API_KEY` | Anthropic intent / MCP agent | Anthropic API key |

---

## Examples

### LangChain integration

```python
from langchain_core.tools import tool
from gatekeeper import protect
from gatekeeper.adapters import MockAdapter

@tool
def pay_vendor(amount: int, merchant_id: str, category: str, purpose: str) -> str:
    """Request a vendor payment. FinGate authorizes or blocks it."""
    intent = PaymentIntent(agent_id="my-agent", amount=amount, ...)
    result = safe_payment(intent)
    if result.decision.is_approve():
        return f"Approved. Link: {result.payment_link}"
    return f"Blocked: {result.decision.reason}"
```

Run: `python examples/langchain_agent/agent.py`

### MCP raw agent

```python
from gatekeeper import GateKeeperMCPTool

tool = GateKeeperMCPTool.from_policy_file("./gatekeeper.policy.yaml")
result = tool.handler({"amount": 8000, "category": "developer_tools", ...})
# result["decision"] == "APPROVE" | "REVIEW" | "BLOCK"
```

Run: `python examples/mcp_raw_agent/agent.py --dry` (offline)

### Cron recurring bot

```python
# Persistent history across process restarts
history = json.loads(Path("history.json").read_text())
safe_payment = protect(execute_payment, policy_config=POLICY, gateway_adapter=MockAdapter(),
                        transaction_history=history)
result = safe_payment(intent)
history.append({"amount": intent.amount, "decision": result.decision.action.value})
Path("history.json").write_text(json.dumps(history))
```

Run: `python examples/cron_recurring_bot/bot.py`

### Demo agent

```bash
python demo_agent/demo_agent.py all
```

---

## FinGate Cloud

Full FastAPI backend + React dashboard for multi-agent management.

```bash
cp .env.example .env           # add Razorpay Test Mode keys
./scripts/generate_dev_certs.sh
docker-compose up -d
docker-compose exec backend python scripts/seed_demo.py
# API: https://localhost/api/health
# Dashboard: http://localhost:3000
# API docs: http://localhost:8000/docs
```

**Auth:** Set `GATEKEEPER_API_KEYS=key1,key2` in `.env`. Pass `X-API-Key: key1` header
on all requests except `/api/health` and webhook endpoints.

---

## Testing

```bash
# Unit tests (76 tests)
python -m pytest tests/ -q

# Backend integration tests (30 tests)
python -m pytest backend/tests/ -q

# Evaluation suite (100 cases — precision, recall, FPR, latency)
python evals/run_tests.py

# Generate fresh eval dataset
python evals/generate_dataset.py
```

**Eval metrics:** TP / TN / FP / FN, precision, recall, false-positive rate,
amount-weighted false negatives, latency p50 / p95. Labels are deterministic-only
(no LLM in eval).

---

## Architecture

```
PaymentIntent → Policy Engine → Risk Engine → Decision Engine → Payment Gateway
                    ↓               ↓              ↓
             Deterministic     Explainable    APPROVE / REVIEW / BLOCK
             rules only       weighted         fail-closed
             (no LLM)         score 0–100
```

**Core components:**

| Component | File | Role |
|---|---|---|
| Policy Engine | `gatekeeper/policy_engine.py` | Deterministic checks (amount, category, merchant, country, velocity) |
| Risk Engine | `gatekeeper/risk_engine.py` | 0–100 score from 5 weighted features, per-feature contributions |
| Decision Engine | `gatekeeper/decision_engine.py` | Final authority. Hard violations → BLOCK. Fail-closed on errors |
| Intent Analyzer | `gatekeeper/intent/` | Optional LLM semantic analysis (Gemini / Anthropic presets). Advisory only |
| Protect wrapper | `gatekeeper/protect.py` | Primary API — wraps any payment function |
| MCP tool | `gatekeeper/mcp_tool.py` | `evaluate_payment` tool, Anthropic tool_use spec |
| Adapters | `gatekeeper/adapters/` | `PaymentGatewayAdapter`: Mock, Razorpay, Stripe |
| Audit | `gatekeeper/audit/` | JSONL audit backend (pluggable) |
| CLI | `gatekeeper/cli.py` | `init`, `validate-policy`, `test-payment` |

---

## Security Guarantees

- **LLM never authorizes** — intent analyzer provides structured data; never decisions
- **Fail-closed** — missing data, errors, or uncertainty → `BLOCK`, never silent `APPROVE`
- **Deterministic override** — hard policy rules override all other inputs
- **Razorpay isolated** — payment API contacted only after explicit `APPROVE`
- **Explainable decisions** — every decision includes full reasoning and feature breakdown
- **Webhook security** — HMAC signature verification, idempotent processing

---

## Limitations

- Razorpay Test Mode only; no real money movement in the demo
- Daily limit is a rolling 24h window via transaction history — persist this in production
- Dashboard API auth uses static keys (no user accounts/roles)
- Local HTTPS uses self-signed certs (swap real certs into `nginx/certs/` for production)

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, test instructions,
code style guidelines, and the PR process.

---

## Changelog

### v0.2.0 — Current

- Package renamed to **FinGate** (`fingate` on PyPI)
- `protect()` middleware wrapper — primary API
- `evaluate_payment_intent()` — dry-run evaluation
- `GateKeeperMCPTool` — MCP tool for Claude-style agents
- 76 unit tests + 100-case evaluation suite
- Razorpay and Stripe adapters
- Gemini and Anthropic intent analyzer presets
- JSONL audit backend
- FinGate Cloud: FastAPI + React dashboard
- Demo agent with 4 scenarios
- `CONTRIBUTING.md` and `LICENSE`

---

## License

MIT — see [LICENSE](LICENSE) file.
