Implements QONTO-WP-0002 (policy-gated Qonto REST service with audit logging, rate limiting, and credential handling) and the ADHOC-2026-07-21 follow-up (fixture-backed local smoke mode, repo classification metadata). Marks QONTO-WP-0001/0002 and the ad-hoc workplan finished, and regenerates WORK-RECORDS.md and the ADHOC workplan's state_hub_workstream_id via fix-consistency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
183 lines
4.2 KiB
Markdown
183 lines
4.2 KiB
Markdown
# Qonto Assistant Operator Runbook
|
|
|
|
## What this Phase 1 service does
|
|
|
|
`qonto-assistant` is the read-only REST surface for governed Qonto access.
|
|
Phase 1 ships:
|
|
|
|
- policy-gated `GET /v1/accounts`
|
|
- policy-gated `GET /v1/transactions`
|
|
- policy-gated `GET /v1/snapshot`
|
|
- structured audit events without secrets
|
|
- env-backed or OpenBao-CLI-backed credential loading
|
|
|
|
Spend, transfer, card, invoicing, payment-link, and other volume-cost actions
|
|
remain denied by policy.
|
|
|
|
## Preferred local workflow
|
|
|
|
If `make` and `python3 -m venv` are available:
|
|
|
|
```bash
|
|
make install-dev
|
|
make test
|
|
make run
|
|
```
|
|
|
|
The API then listens on `http://127.0.0.1:8080`.
|
|
|
|
## Verified fallback on this workstation
|
|
|
|
This workstation currently lacks:
|
|
|
|
- `make`
|
|
- `python3 -m venv` support (`ensurepip` missing)
|
|
- `python3 -m pip`
|
|
|
|
Use an existing fleet virtualenv that already contains FastAPI/httpx/pytest:
|
|
|
|
```bash
|
|
PYTHONPATH=src ../state-hub/.venv/bin/python -m pytest
|
|
python3 -m compileall src tests
|
|
```
|
|
|
|
This fallback was used to verify the current implementation.
|
|
|
|
## Credential sources
|
|
|
|
### Option A: env-injected credentials
|
|
|
|
Provide either:
|
|
|
|
- `API_USER` + `API_KEY`
|
|
- or `QONTO_ORGANIZATION_ID` + `QONTO_API_KEY`
|
|
|
|
Example:
|
|
|
|
```bash
|
|
export API_USER='...'
|
|
export API_KEY='...'
|
|
```
|
|
|
|
### Option B: OpenBao CLI fetch inside the service
|
|
|
|
Set:
|
|
|
|
```bash
|
|
export QONTO_CREDENTIAL_SOURCE=bao-cli
|
|
export QONTO_OPENBAO_PATH=tenants/binky/qonto-api
|
|
export QONTO_OPENBAO_COMMAND=bao
|
|
```
|
|
|
|
The service then shells out to `bao kv get -field=...` and caches the
|
|
credentials in memory for a short TTL.
|
|
|
|
## Start the API
|
|
|
|
Preferred:
|
|
|
|
```bash
|
|
make run
|
|
```
|
|
|
|
Fallback:
|
|
|
|
```bash
|
|
PYTHONPATH=src ../state-hub/.venv/bin/python -m qonto_assistant.main
|
|
```
|
|
|
|
## Fixture-backed local mode
|
|
|
|
For local smoke work without real bank credentials:
|
|
|
|
```bash
|
|
export QONTO_FIXTURE_DIR=tests/fixtures/qonto
|
|
PYTHONPATH=src ../state-hub/.venv/bin/python -m qonto_assistant.main
|
|
```
|
|
|
|
In this mode the service serves canned Qonto organization and transaction
|
|
payloads from `tests/fixtures/qonto/`.
|
|
|
|
## One-command HTTP smoke
|
|
|
|
```bash
|
|
../state-hub/.venv/bin/python scripts/smoke_rest_api.py \
|
|
--python ../state-hub/.venv/bin/python
|
|
```
|
|
|
|
This starts the service on a random local port against the fixture payloads,
|
|
checks `/v1/health`, `/v1/accounts`, a recent `31`-day snapshot, and a wider
|
|
`90`-day cost-review snapshot, then shuts the process down.
|
|
|
|
## Example calls
|
|
|
|
Minimal local call:
|
|
|
|
```bash
|
|
python3 - <<'PY'
|
|
import json
|
|
import urllib.request
|
|
|
|
req = urllib.request.Request(
|
|
"http://127.0.0.1:8080/v1/accounts",
|
|
headers={"X-Actor-ID": "local-operator", "X-Tenant-ID": "binky"},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
print(json.dumps(json.load(resp), indent=2))
|
|
PY
|
|
```
|
|
|
|
Transactions view:
|
|
|
|
```bash
|
|
python3 - <<'PY'
|
|
import json
|
|
import urllib.request
|
|
|
|
req = urllib.request.Request(
|
|
"http://127.0.0.1:8080/v1/transactions?page_size=50&window_days=31",
|
|
headers={"X-Actor-ID": "finance-steward", "X-Tenant-ID": "binky"},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
print(json.dumps(json.load(resp), indent=2))
|
|
PY
|
|
```
|
|
|
|
Snapshot for CostRunRate refresh:
|
|
|
|
```bash
|
|
python3 - <<'PY'
|
|
import json
|
|
import urllib.request
|
|
|
|
req = urllib.request.Request(
|
|
"http://127.0.0.1:8080/v1/snapshot?window_days=90&page_size=50",
|
|
headers={"X-Actor-ID": "finance-steward", "X-Tenant-ID": "binky"},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
print(json.dumps(json.load(resp), indent=2))
|
|
PY
|
|
```
|
|
|
|
Use `window_days=31` for a recent-activity view. Use `window_days=90` or `93`
|
|
when recurring fixed-cost hints are required.
|
|
|
|
## CostRunRate refresh path
|
|
|
|
`binky-control` should consume `GET /v1/snapshot` and extract:
|
|
|
|
- redacted organization/account summary
|
|
- recent transactions
|
|
- recurring debit hints for fixed-cost review
|
|
|
|
The repo does not write directly into `binky-control/finance/CostRunRate.md`.
|
|
That consumer-side write remains outside this repo.
|
|
|
|
## Safety notes
|
|
|
|
- Never print or commit `API_KEY`.
|
|
- Prefer `X-Tenant-ID: binky` explicitly even in single-tenant dogfood.
|
|
- Audit output is metadata-only; account identifiers stay redacted by default.
|
|
- The service supports a `bearer` auth mode for future upstream evolution, but
|
|
the current dogfood path remains `legacy_api_key` because that is the proven
|
|
BINKY-WP-0005 header mode.
|