@AGENTS.md

# GrantFlow — Claude Code Instructions

## What this project is

GrantFlow is an agent-native API for governed grant proposal operations.
Generation runs behind grounding gates and human-in-the-loop checkpoints — it is not autonomous.
It is an API/control layer — not a chatbot, not a generic SaaS dashboard, not a CRM.

## Before making changes

Read in this order:
1. `AGENTS.md` — canonical agent integration guide and operational rules
2. `docs/agents/error-contract.md` — structured error shape and all error codes
3. `docs/agents/trust-report.md` — what GrantFlow verifies vs what is operator responsibility
4. `pyproject.toml` — dependencies and Python version constraints (3.11–3.13)

## Stack

- Python 3.11–3.13 · FastAPI · LangGraph · ChromaDB · Redis · Pydantic v2
- Line length: 120 characters (Ruff / Black)
- Tests: pytest · `grantflow/tests/`
- Fixtures: `grantflow/tests/fixtures/`

## Running tests

```bash
make bootstrap-dev && source .venv/bin/activate
make qa-fast
```

To run a specific test file:

```bash
.venv/bin/python -m pytest grantflow/tests/ -x -q
```

If you change `public_job_quality_payload`, regenerate the golden fixture:

```bash
python -c "
import json
from grantflow.tests.contracts_helpers import sample_quality_contract_job
from grantflow.api.public_views import public_job_quality_payload
job = sample_quality_contract_job()
print(json.dumps(public_job_quality_payload('job-quality-1', job), indent=2))
" > grantflow/tests/fixtures/public_job_quality_payload_golden.json
```

## Key files

| File | Purpose |
|---|---|
| `grantflow/api/errors.py` | `agent_error_detail`, `raise_agent_error`, `guard_agent_write` |
| `grantflow/api/routes/agents.py` | Auth, onboarding, `.well-known/` endpoints |
| `grantflow/api/routes/jobs.py` | Generate, status, quality, events endpoints |
| `grantflow/api/public_views.py` | All public payload builders including `trust_summary` |
| `grantflow/api/idempotency.py` | Idempotency key enforcement (in-process) |
| `grantflow/api/security.py` | Self-serve API keys, OAuth, JTI denylist |
| `grantflow/mcp/tools.py` | MCP tool client — propagates `X-Request-ID` |
| `grantflow/agents/conformance.py` | Conformance smoke suite — run against live server |

## Do not build

- Chatbot UI
- Generic SaaS dashboard or CRM
- Donor portal submission bot
- Enterprise IAM inside the app
- Billing system before pilot proof
- Marketplace or generic agent platform

## Error contract

All agent-facing errors follow `{ detail: { code, message, retryable, next_action } }`.
Use `raise_agent_error()` or `agent_error_detail()` from `grantflow/api/errors.py`.
Never return a plain string detail on agent-facing endpoints.

## Revocation behavior

Credential revocation writes to both an in-process JTI denylist
(`_REVOKED_AGENT_CREDENTIAL_JTIS` in `security.py`) and Redis when
`GRANTFLOW_REDIS_URL` is configured. Revoke responses report
`revocation_scope` (`"redis_and_in_process"` or `"in_process"`) and
`restart_safe` (`true` only when Redis is available) — derive both from
`redis_available()`, never hardcode them.
Without Redis, revocation does not survive process restarts; see
`docs/deployment/credential-revocation.md` for gateway-layer enforcement.

## Commit style

```
feat(scope): short imperative description
fix(scope): ...
chore(scope): ...
```

Scope examples: `agent-contract`, `trust-report`, `revocation`, `mcp`, `conformance`.
