Skip to main content
Glama
README.md
# OrderGuard

**Find → Verify → Pay → Prove**

> The agent can be wrong. The execution cannot be unproven.

**Live**:[frontend-kappa-rouge-88.vercel.app](https://frontend-kappa-rouge-88.vercel.app)
 . backend:
[orderguard-backend-9191.onrender.com](https://orderguard-backend-9191.onrender.com)

OrderGuard is a deterministic financial-authorization boundary around a
probabilistic agent. Claude (or any MCP client) may search, read connector
data, and recommend a candidate — it never holds a Razorpay credential and
never receives a payment-execution tool, at the Python import level as well
as the tool-list level. After explicit user selection, OrderGuard re-reads
authoritative merchant state, evaluates 13 deterministic gates, mints a
short-lived single-use Execution Capability, and only the Secret Executor —
the one module in the codebase that ever touches a Razorpay credential — may
consume that capability to create a real Razorpay test-mode order. After
payment, 9 more gates independently re-verify Razorpay's own record before
anything is called captured.

```text
user intent → agent proposal → fresh merchant re-read → 13 pre-payment gates
  → Execution Capability (single-use, atomic) → Secret Executor
  → Razorpay test order → independent payment verification
  → 9 post-payment gates → ledger finalization → signed evidence
```

## Current verified state

Generated by `make test-report && make eval && make feature-matrix` against
the current commit — never typed by hand, see `results/*.json`:

- **811 tests pass**, 0 failed (`results/test_report.json`).
- **40 shipped features**, each naming the file/function that implements it
  (`results/feature_matrix.json`).
- Fixed-fifty adversarial cart-integrity: **0% false-match**.
- Baseline comparison, same scenario set: `no_guard` and `confirm_only` both
  100% unsafe acceptance and leak the full ₹20,983.73 exposed; OrderGuard is
  **0% unsafe acceptance, ₹0 leaked** of that same exposure
  (`results/latest.json`).
- Architecture invariants enforced as tests, not just claimed in docs
  (`tests/test_architecture_boundaries.py`): the agent package cannot
  import payment code; exactly one file in the tree ever constructs a
  `RazorpayClient`; exactly one file ever issues an Execution Capability;
  exactly one file ever consumes one.
- Execution Capability replay protection proven under genuine concurrent
  access — 50 real OS threads racing one capability, exactly 1 wins
  (`tests/test_capability.py`), with a companion mutation test showing the
  naive (non-atomic) pattern genuinely over-consumes under the same load.

Known, stated limitations — not glossed over — are in
[Implementation audit](docs/IMPLEMENTATION_AUDIT.md) and
[What broke](WHAT_BROKE.md).

## The product

The React app (`frontend/`) has seven tabs, each backed by real code — none
of them a static mockup:

| Tab | What it actually does |
|---|---|
| **Mission** | Natural-language agent flow. Type an intent, watch the real mission trace (capability → connector → tool call → result) build live, approve or reject each proposed cart write. |
| **Shop** | The direct commerce path against FreshCart (this project's own test merchant) — the one connector with a fully closed, real Razorpay test-mode payment proven end to end. |
| **Connectors** | Real, independently-checked health per connector — not "a token exists," but "a live tool call actually succeeded," polled every 15s. See the table below for what's live today. |
| **Attack Lab** | Runs the same 37 adversarial scenarios from `results/latest.json` against OrderGuard live, in the browser, per-attack. |
| **Evidence** | The hash-chained audit log and signed Ed25519 authorizations — independently re-verified on load, not read from a cached badge. |
| **Features** | All 40 shipped features from `results/feature_matrix.json`, each linking the file/function that implements it. |
| **Eval** | The benchmark numbers in this README, rendered from the same `results/*.json` this README quotes — one source of truth, two surfaces. |

### Connectors, real status

| Connector | Category | Auth | Real capability today |
|---|---|---|---|
| **Swiggy Instamart** | Grocery | OAuth (backend-owned) | Search, read cart, **write cart** — live-proven, `docs/CONNECTORS.md` |
| **Swiggy Food** | Food delivery | OAuth (backend-owned) | Search restaurants/menu, read cart, write cart |
| **Shopify** (any verified store) | General commerce | None — public per-store MCP | Search catalog, read/write cart — no key, no approval, verified live against real Indian D2C stores |
| **GitHub** | Dev tasks | Personal access token | Read-only (`list_issues`) — the required non-commerce proof, chosen specifically because its auth is a PAT, not a full OAuth app |

Every connector's *checkout* tool (a real payment/order-placement action) is
R3 — structurally excluded from the agent's own tool list at the Python
level, never merely filtered at runtime. See `src/orderguard/agent/tools.py`
and `tests/test_architecture_boundaries.py`.

## Run locally

```bash
cp .env.example .env   # fill in test-mode Razorpay keys — see that file
uv run pytest -q
make eval
make feature-matrix
make dev
```

`make dev` starts the backend (`:8000`) and the real React frontend
(`:5173`) together — open **http://127.0.0.1:5173**. Ctrl-C stops both.
There is no server-rendered `/app` route anymore; the product is the React
app under `frontend/`.

Runtime credentials stay server-side. Copy `.env.example` to `.env`, use
Razorpay **test-mode** keys only, and generate connector encryption material
as documented in that file. BYOK Anthropic keys live only in process memory
and can be forgotten explicitly.

## Deploy

Live today as **four** separate services, all on the free tier, wired
together by URL — not a single container:

- `orderguard-backend` (Render, Python/FastAPI) — the only process holding
  a Razorpay credential.
- `orderguard-freshcart` (Render, Python/FastAPI) — the demo merchant
  (`demo_store/app.py`), deployed separately on purpose: it is the one
  store this project can actually take a payment against, and it needed to
  be reachable over the network like any other merchant, not imported as a
  library (see [`FAILURE_LOG.md`](FAILURE_LOG.md) F-034 for the outage this
  caused the first time it was missed).
- `orderguard-frontend` (Render static site) and a second static build on
  Vercel — same code, two hosts, because Render's static-site product has
  no API-configurable SPA rewrite rule while Vercel applies one from
  `frontend/vercel.json` automatically; both point at the same backend.
- **Neon** (free-tier Postgres) — every table this project owns (audit
  chain, ledger, capability store, signed-authorization records + the
  Ed25519 signing key, connector accounts, chat memory, webhook log,
  connector log, custom connectors) lives here in production, via
  `DATABASE_URL` (`src/orderguard/db.py`).

[`DEPLOY.md`](DEPLOY.md) walks through reproducing this from a fresh
account. Same honest scope as running locally: test-mode Razorpay,
single-tenant, no auth. Real secrets are entered directly into each
platform's own dashboard, never through this repo or an AI assistant.

**Why Postgres and not a Render disk**: Render's free compute plan does
not support persistent disks at all — discovered by trying to attach one.
Every redeploy was wiping every SQLite file, including a real Swiggy OAuth
connection right after it was created (see
[`FAILURE_LOG.md`](FAILURE_LOG.md) F-035). The obvious "just use a
different free host" alternatives (Fly.io, Railway, Koyeb) turned out to
have dropped free persistent volume storage too, as of this build — Neon's
free, always-on hosted Postgres was the option that cost nothing and kept
real durability. Verified live: a deliberate manual redeploy of the
backend, and the connector accounts made before it were still there after.

## Security boundary

```text
Claude / MCP client → eligible R0 connector reads → candidates
user selection → fresh merchant cart re-read → 13 deterministic gates
→ Execution Capability (single-use, atomic consumption)
→ Secret Executor (sole holder of the Razorpay credential)
→ Razorpay test order → independent server-side payment verification
→ 9 post-payment gates → exactly-once ledger finalization → signed evidence
```

R3 (financial) tools fail with `FinancialToolExposureError`; they are never
silently removed and never offered to either agent runtime. The agent
package cannot import the payment/executor/capability modules at all —
enforced by `tests/test_architecture_boundaries.py`, not only documented.

**Honest limit, stated once here rather than left implicit**: this is
import discipline plus a database-level atomic gate, not a cryptographic
capability nothing could forge, and not a separate process/service. See
`src/orderguard/capability.py` and `executor.py`'s own docstrings for
exactly what is and is not proven.

## Documentation index

| Doc | Covers |
|---|---|
| [ARCHITECTURE.md](ARCHITECTURE.md) | Trust boundaries, request paths, what's additive vs. load-bearing |
| [SECURITY.md](SECURITY.md) | The full security boundary, stated with its own honest limits |
| [docs/GATES.md](docs/GATES.md) | All 22 gates (13 pre-payment + 9 post-payment), one by one |
| [docs/API_CONTRACTS.md](docs/API_CONTRACTS.md) | Every API contract this project holds itself to (money units, error shapes, idempotency) |
| [docs/CONNECTORS.md](docs/CONNECTORS.md) | Per-connector verification evidence, dated, with what was actually tested |
| [docs/AGENT_WORKFLOWS.md](docs/AGENT_WORKFLOWS.md) | How a mission actually moves through the orchestrator |
| [docs/FEATURE_MATRIX.md](docs/FEATURE_MATRIX.md) | All 40 features, each with its implementing file/function |
| [EVALUATION.md](EVALUATION.md) | How to reproduce every number in this README |
| [docs/DEBUGGING.md](docs/DEBUGGING.md) | How to actually debug this system when something's wrong |
| [DEMO.md](DEMO.md) | The demo script this project's own walkthroughs follow |
| [LIMITATIONS.md](LIMITATIONS.md) | What this project does **not** prove — written before results existed |
| [WHAT_BROKE.md](WHAT_BROKE.md) | Short version of real failures hit building this |
| [FAILURE_LOG.md](FAILURE_LOG.md) | The full, dated incident log — 40 entries, root-caused, not smoothed over |
| [DECISIONS.md](DECISIONS.md) | Every real design decision, with the alternative considered and why it lost |
| [docs/IMPLEMENTATION_AUDIT.md](docs/IMPLEMENTATION_AUDIT.md) | An honest audit of what's actually implemented vs. claimed |