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>
160 lines
5.2 KiB
Markdown
160 lines
5.2 KiB
Markdown
---
|
|
id: QONTO-WP-0002
|
|
type: workplan
|
|
title: "Phase 1 — policy kernel and read-only REST"
|
|
domain: infotech
|
|
repo: qonto-assistant
|
|
status: finished
|
|
owner: codex
|
|
topic_slug: the-custodian
|
|
created: "2026-07-21"
|
|
updated: "2026-07-21"
|
|
state_hub_workstream_id: "1540afc2-219e-4d95-96f5-4b45fc6a7aaa"
|
|
---
|
|
|
|
# Phase 1 — policy kernel and read-only REST
|
|
|
|
Execute **Phase 1** of `specs/ArchitectureBlueprint.md`: a service skeleton
|
|
with a protocol-neutral capability core and shared
|
|
`decide(request, claims) → Allow|Deny` policy kernel plus a minimal REST
|
|
surface for org/accounts/transactions/snapshot. No MCP yet (Phase 2). No
|
|
spend or volume-cost tools — default deny. Baseline guardrails ship in this
|
|
phase: bounded pagination/timeouts, basic rate limiting, bounded concurrency,
|
|
and redaction tests.
|
|
|
|
**Depends on:** live OpenBao path `tenants/binky/qonto-api` (BINKY-WP-0005 /
|
|
CCR-2026-0008) — already provisioned.
|
|
|
|
## Task: Choose runtime skeleton and layout
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "f9e129f3-5bd4-43e1-b7a0-281e4d3dec2a"
|
|
```
|
|
|
|
Pick implementation stack (recommendation: Python 3.12 + FastAPI unless a
|
|
fleet standard dictates otherwise). Scaffold package layout, `pyproject.toml`
|
|
(or equivalent), Makefile targets (`test`, `lint`, `run`), and document
|
|
commands in `AGENTS.md` / complete QONTO-WP-0001-T02.
|
|
|
|
Done when: `make test` (or documented equivalent) runs an empty/smoke suite;
|
|
layout matches blueprint components (protocol adapters, capability core,
|
|
policy, qonto client, api, audit).
|
|
|
|
## Task: Policy kernel — default-deny no-spend / no-volume-cost
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "552ff651-dc66-4e65-97fe-3ec26652bbdd"
|
|
```
|
|
|
|
Implement declarative policy (YAML or equivalent) + pure decision function:
|
|
|
|
- default **deny**
|
|
- allow only v1 read capability ids (`org_summary`, `list_transactions`,
|
|
`cost_run_rate_hints`, `snapshot_bundle`)
|
|
- hard deny classes: `spend`, `volume_cost`, `credential_exfil`
|
|
- stable deny reasons for authz, tenant scope, argument constraint, and
|
|
credential exfil cases
|
|
- unit tests: allow known reads; deny transfer/card/invoice-shaped tools and
|
|
suspicious args even if somehow invoked
|
|
|
|
Done when: policy tests pass in CI/local; no network required.
|
|
|
|
## Task: Qonto REST client (credential inject, no secret log)
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "be3aa7b6-f28c-4436-bd5d-d6940de6c2ce"
|
|
```
|
|
|
|
Implement thirdparty client using `Authorization: login:key` (fields
|
|
`API_USER`/`API_KEY` from env or OpenBao fetch helper). Map:
|
|
|
|
- organization + bank accounts
|
|
- paginated transactions with hard caps
|
|
- bounded timeouts and conservative retry behavior
|
|
- never log Authorization or key material
|
|
|
|
Support env inject for tests (`QONTO_API_KEY`/`QONTO_ORGANIZATION_ID` or
|
|
`API_KEY`/`API_USER`) and document OpenBao fetch for operators. Production
|
|
path fetches through a dedicated assistant runtime role only.
|
|
|
|
Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
|
|
|
## Task: REST API surface
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T04
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "678b0b26-15af-4037-849f-d24d320588ac"
|
|
```
|
|
|
|
Expose JSON endpoints that all run through the policy kernel:
|
|
|
|
| Method | Path | Capability |
|
|
| --- | --- | --- |
|
|
| GET | `/v1/health` | no bank call |
|
|
| GET | `/v1/accounts` | org_summary |
|
|
| GET | `/v1/transactions` | list_transactions (capped) |
|
|
| GET | `/v1/snapshot` | snapshot_bundle (composed allowed reads) |
|
|
|
|
Done when: OpenAPI or documented curl examples; integration test with mock
|
|
client; deny paths return 403 with stable error code; route-to-capability
|
|
mapping is explicit and shared with future MCP.
|
|
|
|
## Task: Audit metadata (no secrets)
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T05
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "4a42dff1-1281-4cc1-ba6a-24702bce7dc9"
|
|
```
|
|
|
|
Log or emit structured audit events: actor (if present), capability, decision,
|
|
deny_reason, latency, upstream HTTP status, and policy version. Never secret
|
|
fields. Prefer structured stdout JSON (or equivalent sink) on the hot path;
|
|
State Hub progress stays optional roll-up evidence for dogfood runs, not
|
|
per-call audit storage.
|
|
|
|
Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
|
|
|
## Task: Operator runbook + CostRunRate refresh path
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T06
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "7b7ca0f7-f523-473e-b3f6-fe0564f54ed5"
|
|
```
|
|
|
|
Document how binky-control refreshes `finance/CostRunRate.md` via
|
|
`GET /v1/snapshot` (or equivalent) with OpenBao-backed service start. Link from
|
|
README. Optional thin script under `scripts/`.
|
|
|
|
Done when: runbook in `docs/` or README section; dry-run instructions without
|
|
pasting keys into chat.
|
|
|
|
## Task: Closure review
|
|
|
|
```task
|
|
id: QONTO-WP-0002-T07
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "9b99ba05-99f2-4624-9044-89bf37055434"
|
|
```
|
|
|
|
**Done 2026-07-21:** Phase 1 landed in `src/qonto_assistant/` with a
|
|
FastAPI-based REST service, policy YAML, Qonto client, audit layer, rate
|
|
limits, concurrency bounds, and operator runbook. Verified with
|
|
`PYTHONPATH=src ../state-hub/.venv/bin/python -m pytest` (`12 passed`) plus
|
|
`python3 -m compileall src tests`. Phase 2 seed remains the MCP surface on the
|
|
same capability core and policy engine. Run `statehub fix-consistency`.
|