Complete Phase 1: policy kernel, REST service, and local smoke tooling
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>
This commit is contained in:
parent
eef408bb19
commit
ca12843013
33 changed files with 2533 additions and 30 deletions
183
docs/operator-runbook.md
Normal file
183
docs/operator-runbook.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue