Bootstrap qonto-assistant: intent, blueprint, research, workplans
Author INTENT and SCOPE; persist ArchitectureBlueprint under specs/ and MCP gateway research under research/. Register with State Hub (QONTO-WP- prefix); seed QONTO-WP-0001 (bootstrap, finished) and QONTO-WP-0002 (Phase 1 REST policy kernel, ready).
This commit is contained in:
parent
bae0734564
commit
e1ab23f83e
10 changed files with 1330 additions and 1 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -162,3 +162,9 @@ cython_debug/
|
|||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
|
||||
# state-hub: track .claude/rules
|
||||
# Claude Code local state (track shared rules; ignore machine-specific files)
|
||||
.claude/*
|
||||
!.claude/rules/
|
||||
!.claude/rules/*.md
|
||||
|
|
|
|||
206
AGENTS.md
Normal file
206
AGENTS.md
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
# qonto-assistant — Agent Instructions
|
||||
|
||||
## Repo Identity
|
||||
|
||||
**Purpose:** Policy-governed Qonto domain REST+MCP assistant: sole bank-key consumer, default-deny no-spend/no-volume-cost policy for multi-harness finance awareness.
|
||||
|
||||
**Domain:** infotech
|
||||
**Repo slug:** qonto-assistant
|
||||
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
|
||||
**Workplan prefix:** `QONTO-WP-`
|
||||
|
||||
---
|
||||
|
||||
## State Hub Integration
|
||||
|
||||
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
||||
there is no MCP server for Codex agents.
|
||||
|
||||
| Context | URL |
|
||||
|---------|-----|
|
||||
| Local workstation | `http://127.0.0.1:8000` |
|
||||
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||
| Optional local edge relay | http://127.0.0.1:18080 |
|
||||
|
||||
When an operator has enabled the edge relay, set API_BASE to the relay URL.
|
||||
Queueable writes return an explicit queued receipt if the central hub is
|
||||
unreachable. Treat that as pending local evidence, then ask the operator to run
|
||||
statehub outbox status/replay after connectivity returns.
|
||||
|
||||
### Orient at session start
|
||||
|
||||
```bash
|
||||
# Offline brief — works without hub connection
|
||||
cat .custodian-brief.md
|
||||
|
||||
# Active workplans for this domain
|
||||
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
|
||||
| python3 -m json.tool
|
||||
|
||||
# Check inbox
|
||||
curl -s "http://127.0.0.1:8000/messages/?to_agent=qonto-assistant&unread_only=true" \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
Mark a message read:
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||
-H "Content-Type: application/json" -d '{}'
|
||||
```
|
||||
|
||||
### Log progress (required at session close)
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"summary": "what was done",
|
||||
"event_type": "note",
|
||||
"author": "codex",
|
||||
"workplan_id": "<uuid>",
|
||||
"task_id": "<uuid>"
|
||||
}'
|
||||
```
|
||||
|
||||
Omit `workplan_id` / `task_id` when not applicable.
|
||||
|
||||
### Update task status
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "progress"}'
|
||||
# values: wait | todo | progress | done | cancel
|
||||
```
|
||||
|
||||
### Flag a task for human review
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"needs_human": true, "intervention_note": "reason"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Protocol
|
||||
|
||||
**Start:**
|
||||
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
|
||||
2. Check inbox: `GET /messages/?to_agent=qonto-assistant&unread_only=true`; mark read
|
||||
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
||||
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
|
||||
|
||||
**During work:**
|
||||
- Update task statuses in workplan files as tasks progress
|
||||
- Record significant decisions via `POST /decisions/`
|
||||
|
||||
**Close:**
|
||||
1. Update workplan file task statuses to reflect progress
|
||||
2. Log: `POST /progress/` with a summary of what changed
|
||||
3. After workplan file changes, run:
|
||||
```bash
|
||||
statehub fix-consistency
|
||||
```
|
||||
Coding agents should run this directly; ask the operator only if the CLI or
|
||||
State Hub API is unavailable. This syncs task status from files into the hub DB.
|
||||
|
||||
---
|
||||
|
||||
{CREDENTIAL_ROUTING}
|
||||
|
||||
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||
<!-- Append repo-specific agent instructions below this marker.
|
||||
The state-hub template sync preserves content after this line. -->
|
||||
|
||||
## Repo-specific orientation
|
||||
|
||||
| Doc | Role |
|
||||
| --- | --- |
|
||||
| `INTENT.md` | Why / never-become |
|
||||
| `specs/ArchitectureBlueprint.md` | Architecture + phases |
|
||||
| `research/` | Pattern research |
|
||||
| `SCOPE.md` | In/out of scope |
|
||||
|
||||
**Security:** Never print or commit Qonto API keys. Service alone fetches
|
||||
`tenants/binky/qonto-api` (`API_KEY`, `API_USER`). Clients use assistant URL +
|
||||
workload identity only. Policy v1: **no spend / no volume-cost**.
|
||||
|
||||
**Local verification (until QONTO-WP-0002 lands runtime):**
|
||||
|
||||
```bash
|
||||
# Workplan frontmatter ↔ hub
|
||||
statehub fix-consistency
|
||||
|
||||
# Docs present
|
||||
test -f INTENT.md && test -f specs/ArchitectureBlueprint.md
|
||||
```
|
||||
|
||||
After Phase 1 skeleton exists, prefer `make test` / `make lint` as defined in
|
||||
the Makefile from QONTO-WP-0002-T01.
|
||||
|
||||
---
|
||||
|
||||
## Workplan Convention (ADR-001)
|
||||
|
||||
Work items originate as files in this repo — not in the hub. The hub is a
|
||||
read/cache/index layer that rebuilds from files.
|
||||
|
||||
**File location:** `workplans/QONTO-WP-NNNN-<slug>.md`
|
||||
|
||||
**Archived location:** finished workplans may move to
|
||||
`workplans/archived/YYMMDD-QONTO-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
|
||||
the completion/archive date; the frontmatter `id` does not change.
|
||||
|
||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
|
||||
this only for low-risk work completed directly; create a normal workplan for
|
||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||
|
||||
**Frontmatter:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: QONTO-WP-NNNN
|
||||
type: workplan
|
||||
title: "..."
|
||||
domain: infotech
|
||||
repo: qonto-assistant
|
||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||
owner: codex
|
||||
topic_slug: ...
|
||||
created: "YYYY-MM-DD"
|
||||
updated: "YYYY-MM-DD"
|
||||
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
|
||||
---
|
||||
```
|
||||
|
||||
Use `proposed` for a new draft, `ready` after review against current repo
|
||||
state, and `finished` after implementation. `stalled` and `needs_review` are
|
||||
derived health labels, not frontmatter statuses.
|
||||
|
||||
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
|
||||
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
|
||||
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
|
||||
|
||||
**Task block format** (one per `##` section):
|
||||
|
||||
```
|
||||
## Task Title
|
||||
|
||||
` ` `task
|
||||
id: QONTO-WP-NNNN-T01
|
||||
status: wait | todo | progress | done | cancel
|
||||
priority: high | medium | low
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
` ` `
|
||||
|
||||
Task description text.
|
||||
```
|
||||
|
||||
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
|
||||
|
||||
To create a new workplan:
|
||||
1. Write the file following the format above
|
||||
2. Run `statehub fix-consistency` locally; ask the operator only if the CLI or
|
||||
State Hub API is unavailable.
|
||||
135
INTENT.md
Normal file
135
INTENT.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# INTENT
|
||||
|
||||
> This file explains why **qonto-assistant** exists — the problem it solves,
|
||||
> the principle that governs its boundaries, and what it must never become.
|
||||
> Blueprint: `specs/ArchitectureBlueprint.md`. Origin: binky-control
|
||||
> BINKY-WP-0005 (Qonto custody + first read-only pull) and the multi-harness
|
||||
> policy-drift problem (2026-07-21).
|
||||
|
||||
## Why it exists
|
||||
|
||||
Binky (and later other tenants) need agents to **see** bank reality —
|
||||
balances, transactions, cost-run-rate signals — so the company control plane
|
||||
can operate without founder spreadsheet archaeology. Credentials already live
|
||||
in OpenBao (`tenants/binky/qonto-api`); the thirdparty REST API works.
|
||||
|
||||
What fails as scale:
|
||||
|
||||
- Wiring vendor MCP or bank keys into **every** coding agent / harness
|
||||
(Claude, Codex, Cursor, Grok, agent-harness, …) creates **policy drift**.
|
||||
- Qonto API keys are **not** scope-limited server-side: there is no
|
||||
“read-only key.” Spend and volume-cost actions must be blocked **before**
|
||||
the vendor sees them.
|
||||
- Agent-harness is the session policy shell, not the place to reimplement
|
||||
bank rules per runtime. llm-connect is LLM routing, not domain tools.
|
||||
|
||||
**qonto-assistant** exists so that **one governed domain service** is the
|
||||
only component allowed to hold the bank credential and speak Qonto’s
|
||||
protocol. All harnesses are clients of that service. Policy is code + tests
|
||||
in one place.
|
||||
|
||||
## Purpose
|
||||
|
||||
`qonto-assistant` is a **policy-governed Qonto domain assistant**: a dual
|
||||
surface (**REST + MCP**) for agentic and scripted finance awareness under a
|
||||
hard **no spend / no volume-cost** policy (v1).
|
||||
|
||||
Core principle:
|
||||
|
||||
> Harnesses never hold the bank key. The assistant is the choke point.
|
||||
> Default deny. Reads for control-plane awareness; payments stay Red lane
|
||||
> (human in the Qonto app).
|
||||
|
||||
## Primary utility
|
||||
|
||||
### As a multi-harness finance tool
|
||||
|
||||
- Streamable-HTTP **MCP** for interactive agents (one client config, no
|
||||
secrets in MCP configs).
|
||||
- Stable **REST** for rhythm sessions, CostRunRate refresh, CI smoke, and
|
||||
non-MCP clients.
|
||||
- Shared **policy kernel** on every call (tool id + arguments + actor
|
||||
claims) — not connect-time allow-lists alone.
|
||||
|
||||
### As a custody-aware connector
|
||||
|
||||
- Fetches secrets only via the sanctioned OpenBao path (ops-warden catalog
|
||||
`binky-qonto-api` / CCR-2026-0008 pattern).
|
||||
- Optional flex-auth gate: `finance.qonto.read` (and later finer scopes).
|
||||
- Audit metadata (who/what/decision) without logging secret material.
|
||||
|
||||
### As dogfood for Operational Knowledge
|
||||
|
||||
- Internal use on Binky is product evidence (governed bank access for agent
|
||||
fleets). May graduate to multi-tenant `tenants/<slug>/qonto-api` later.
|
||||
|
||||
## Strategic role
|
||||
|
||||
```text
|
||||
OpenBao custody (existing)
|
||||
+ sole Qonto client identity
|
||||
+ default-deny tool catalog
|
||||
+ dual MCP + REST
|
||||
= consistent finance awareness across every harness
|
||||
```
|
||||
|
||||
It sits **below** agent-harness (session runtime) and **beside** other domain
|
||||
connectors (email-connect). It does not replace DATEV/StB/DUO; it complements
|
||||
them with operational bank visibility.
|
||||
|
||||
## Governing principles
|
||||
|
||||
1. **One policy, many clients** — never reimplement bank rules per harness.
|
||||
2. **Default deny** — only explicitly allowed read capabilities exist in v1.
|
||||
3. **No token passthrough** — clients authenticate *to the assistant*; the
|
||||
assistant alone authenticates *to Qonto*.
|
||||
4. **Semantic policy, not vendor dump** — we design a safe catalog; we do
|
||||
not auto-mirror vendor write tools.
|
||||
5. **Platform fit** — flex-auth, OpenBao, ops-warden, State Hub audit; no
|
||||
parallel IAM.
|
||||
6. **Autonomy lanes** — map to binky-control AutonomyPolicy: reads Green/Blue;
|
||||
plan change / transfers / API key minting remain Red.
|
||||
|
||||
## What it must never become
|
||||
|
||||
- **Not a payments engine.** No transfers, SEPA, card issuance, or
|
||||
fee-bearing create operations in v1 (and only via explicit future DEC +
|
||||
Red/Yellow gates thereafter).
|
||||
- **Not a full accounting system.** DATEV / StB / DUO stay authoritative for
|
||||
books.
|
||||
- **Not a generic MCP gateway.** Fleet gateways may sit *in front* later;
|
||||
domain policy lives here.
|
||||
- **Not an LLM router.** Provider selection stays in llm-connect.
|
||||
- **Not a scheduler.** Cadence stays in activity-core / agent-harness.
|
||||
- **Not a secret store.** OpenBao remains custody; this service is a
|
||||
short-lived consumer.
|
||||
- **Not a per-harness plugin bag.** Client integration is thin: URL +
|
||||
workload identity + tool profile name.
|
||||
|
||||
## Relationship to other repos
|
||||
|
||||
| Repo | Relationship |
|
||||
| --- | --- |
|
||||
| `binky-control` | First tenant / consumer; CostRunRate, AutonomyPolicy, business policy source |
|
||||
| `ops-warden` / `railiance-platform` | Credential front door and CCR/policy for `tenants/…/qonto-api` |
|
||||
| `agent-harness` | Grants profile `finance-qonto-read` pointing at this service only |
|
||||
| `llm-connect` | Models that *talk*; this service *acts* on bank data |
|
||||
| `flex-auth` | Authorization decisions for who may call |
|
||||
| `email-connect` | Sibling pattern: purpose-built connector, not raw vendor wiring |
|
||||
|
||||
## Success looks like
|
||||
|
||||
1. Every agent that needs Qonto data uses **only** this service.
|
||||
2. A single policy change (e.g. tighten export) applies fleet-wide.
|
||||
3. No bank API key appears in harness env, MCP client configs, or chat.
|
||||
4. Deny decisions are tested and audited; spend tools are not registered.
|
||||
5. CostRunRate and Finance Steward can refresh from REST without ad-hoc scripts
|
||||
holding secrets.
|
||||
|
||||
## Normative references
|
||||
|
||||
- `specs/ArchitectureBlueprint.md` — architecture and phased delivery
|
||||
- `research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md` — external
|
||||
pattern research
|
||||
- binky-control `integrations/qonto-mcp.md` — custody and first-pull evidence
|
||||
- agent-harness ADR-001 / INTENT — session runtime boundaries
|
||||
24
README.md
24
README.md
|
|
@ -1,3 +1,25 @@
|
|||
# qonto-assistant
|
||||
|
||||
A policy governed qonto domain API and MCP assistant for binky-control.
|
||||
Policy-governed Qonto domain API and MCP assistant for Binky (and later
|
||||
multi-tenant dogfood).
|
||||
|
||||
**One choke point for bank access across every coding agent and harness.**
|
||||
Clients never hold the Qonto API key. v1 policy: **read for awareness only —
|
||||
no spend, no volume-cost actions.**
|
||||
|
||||
## Start here
|
||||
|
||||
| Doc | What |
|
||||
| --- | --- |
|
||||
| [`INTENT.md`](INTENT.md) | Why this exists; boundaries |
|
||||
| [`specs/ArchitectureBlueprint.md`](specs/ArchitectureBlueprint.md) | Architecture, phases, policy model |
|
||||
| [`research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md`](research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md) | External + internal research |
|
||||
|
||||
## Status
|
||||
|
||||
Scaffold + intent + blueprint. Implementation tracked in `workplans/`.
|
||||
|
||||
## Related
|
||||
|
||||
- OpenBao lane: `tenants/binky/qonto-api` (ops-warden `binky-qonto-api`)
|
||||
- First pull / CostRunRate: `binky-control` BINKY-WP-0005
|
||||
|
|
|
|||
55
SCOPE.md
Normal file
55
SCOPE.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# SCOPE
|
||||
|
||||
> Refined after `statehub register` (2026-07-21). Normative intent in
|
||||
> `INTENT.md`; architecture in `specs/ArchitectureBlueprint.md`.
|
||||
|
||||
## One-liner
|
||||
|
||||
Policy-governed Qonto domain **REST + MCP** assistant: sole bank-key consumer
|
||||
and default-deny **no spend / no volume-cost** policy for multi-harness finance
|
||||
awareness.
|
||||
|
||||
## Core idea
|
||||
|
||||
All agents and harnesses talk to **this service** for Qonto. None of them hold
|
||||
the OpenBao bank secret. Policy is enforced here once, not reimplemented per
|
||||
client.
|
||||
|
||||
## In scope
|
||||
|
||||
- Dual surfaces: streamable-HTTP MCP and JSON REST with a **shared policy kernel**
|
||||
- Read capabilities: org/accounts/balances, transactions (bounded), CostRunRate
|
||||
hints / snapshot helpers
|
||||
- OpenBao consumption for `tenants/<tenant>/qonto-api` (dogfood: binky)
|
||||
- Authn of callers (OIDC / workload identity) and optional flex-auth checks
|
||||
- Audit metadata (actor, tool, allow/deny) without secret leakage
|
||||
- Tests for default-deny and spend/volume-cost rejection
|
||||
- Docs for harness client config (no bank secrets)
|
||||
|
||||
## Out of scope (v1 and hard)
|
||||
|
||||
- Payments, transfers, SEPA, card operations, invoicing create, plan changes
|
||||
- Hosting or recommending the hosted Qonto OAuth MCP connector
|
||||
- DATEV / StB / DUO bookkeeping replacement
|
||||
- LLM provider routing (llm-connect)
|
||||
- Agent scheduling (activity-core) or session runtime (agent-harness)
|
||||
- Generic multi-MCP fleet gateway (may sit *in front* later; not this repo’s job)
|
||||
- Storing bank credentials outside OpenBao
|
||||
|
||||
## Current state
|
||||
|
||||
| Artifact | Status |
|
||||
| --- | --- |
|
||||
| INTENT / SCOPE / AGENTS | present |
|
||||
| Architecture blueprint | `specs/ArchitectureBlueprint.md` |
|
||||
| Research notes | `research/2026-07-21-…` |
|
||||
| Runtime implementation | not started (see workplans) |
|
||||
| Upstream custody | live in platform (`binky-qonto-api`, CCR-2026-0008) |
|
||||
|
||||
## Getting oriented
|
||||
|
||||
1. `INTENT.md` — why / boundaries
|
||||
2. `specs/ArchitectureBlueprint.md` — how
|
||||
3. `research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md` — why this shape
|
||||
4. `workplans/` — what to do next
|
||||
5. `AGENTS.md` — State Hub protocol
|
||||
22
WORK-RECORDS.md
Normal file
22
WORK-RECORDS.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Work Records — qonto-assistant
|
||||
|
||||
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
|
||||
> stage 3). Do not edit by hand — edit the source file/block listed for
|
||||
> each record and re-run fix-consistency to refresh this index. Archived
|
||||
> workplans are omitted; closed decisions/intakes/engagements stay listed
|
||||
> so recently-resolved work is still visible. [auto]
|
||||
|
||||
| Kind | ID | Status | Lane | Source |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| workplan | QONTO-WP-0001 | finished | — | workplans/QONTO-WP-0001-statehub-bootstrap.md |
|
||||
| workplan | QONTO-WP-0002 | ready | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0001-T01 | done | — | workplans/QONTO-WP-0001-statehub-bootstrap.md |
|
||||
| task | QONTO-WP-0001-T02 | wait | — | workplans/QONTO-WP-0001-statehub-bootstrap.md |
|
||||
| task | QONTO-WP-0001-T03 | done | — | workplans/QONTO-WP-0001-statehub-bootstrap.md |
|
||||
| task | QONTO-WP-0002-T01 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T02 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T03 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T04 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T05 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T06 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
| task | QONTO-WP-0002-T07 | todo | — | workplans/QONTO-WP-0002-policy-kernel-and-rest.md |
|
||||
172
research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md
Normal file
172
research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Research: MCP gateways and governed domain assistants for bank access
|
||||
|
||||
> Date: 2026-07-21
|
||||
> Purpose: Capture external and internal research that informed
|
||||
> `specs/ArchitectureBlueprint.md` and `INTENT.md`.
|
||||
> Not a product spec — evidence and pattern notes.
|
||||
|
||||
---
|
||||
|
||||
## 1. Question under investigation
|
||||
|
||||
How should multiple coding agents and harnesses interact with Qonto
|
||||
**conveniently and under one policy**, without wiring vendor MCP into each
|
||||
client (policy drift) and without relying on Qonto to enforce
|
||||
read-only / no-spend rules at the API key layer?
|
||||
|
||||
---
|
||||
|
||||
## 2. Internal facts already established (BINKY-WP-0005)
|
||||
|
||||
| Fact | Evidence |
|
||||
| --- | --- |
|
||||
| Credentials live at `tenants/binky/qonto-api` | OpenBao; fields `API_KEY`, `API_USER` |
|
||||
| Thirdparty REST works | First pull 2026-07-21; 122 txs; CostRunRate v2 |
|
||||
| Auth shape | `Authorization: <API_USER>:<API_KEY>` |
|
||||
| Self-hosted MCP env map | `API_KEY`→`QONTO_API_KEY`, `API_USER`→`QONTO_ORGANIZATION_ID` |
|
||||
| API keys not scope-limited | Design note in binky-control `integrations/qonto-mcp.md` |
|
||||
| Hosted OAuth MCP rejected | Corporate access policy: no native connector custody |
|
||||
| Catalog / CCR | ops-warden `binky-qonto-api` active; CCR-2026-0008 active |
|
||||
| Founder policy intent | No spend / no volume-cost actions for agents; payments Red lane |
|
||||
|
||||
---
|
||||
|
||||
## 3. External pattern research (2025–2026 MCP ecosystem)
|
||||
|
||||
### 3.1 MCP gateway / proxy as control plane
|
||||
|
||||
Industry deployments place a **gateway between untrusted MCP clients and
|
||||
tools**:
|
||||
|
||||
| Source | Relevant capability |
|
||||
| --- | --- |
|
||||
| **Envoy AI Gateway MCPRoute** | Multiplex backends; `toolSelector` include/regex; upstream API-key injection so clients never hold vendor secrets; OAuth on gateway; CEL on `tools/call` params (`request.mcp.tool`, `request.mcp.params`) |
|
||||
| **Kong AI Gateway MCP Tool ACLs** | Default-deny; per-consumer tool subsets at gateway (solves all-or-nothing MCP) |
|
||||
| **Portkey / Speakeasy / Permit MCP Gateway** framing | Authn, RBAC, audit every tool call; policy on each call not only at connect time |
|
||||
| **Harness MCP server docs** | Compatible with MCP gateways: centralized credentials, governance, audit, single endpoint |
|
||||
|
||||
**Lesson:** Clients connect to one governed endpoint; the gateway owns
|
||||
credentials, published tool inventory, and policy.
|
||||
|
||||
### 3.2 MCP security best practices
|
||||
|
||||
Synthesized from MCP security best-practice docs and industry guides
|
||||
(confused-deputy / token passthrough / OAuth proxy guidance):
|
||||
|
||||
| Practice | Application |
|
||||
| --- | --- |
|
||||
| No token passthrough | Clients must not present the bank key; assistant is sole Qonto client |
|
||||
| Tool-level scopes | “Access to Qonto” too coarse; allow list_transactions, never create_transfer |
|
||||
| Default deny | Unknown tools fail closed |
|
||||
| JIT escalation for high privilege | Future spend only with explicit re-auth; never session-global write mode |
|
||||
| Audit end-to-end | Actor + tool + decision; no secrets in logs |
|
||||
| Inventory of MCP servers | Fleet must know what is connected (anti shadow-MCP) |
|
||||
|
||||
### 3.3 Gateway vs domain assistant
|
||||
|
||||
| Approach | Strength | Weakness for bank access |
|
||||
| --- | --- | --- |
|
||||
| Generic MCP gateway in front of **vendor** MCP | Fast tool filtering | Vendor catalog still includes write tools; weak **semantic** spend rules; unmaintained self-hosted Qonto MCP |
|
||||
| **Domain assistant** (own MCP + REST, policy inside) | Safe catalog we design; dual protocol; productizable | Build cost |
|
||||
| Per-client allow-lists only | Zero new service | Guaranteed policy drift |
|
||||
|
||||
**Conclusion adopted in blueprint:** build domain assistant; treat generic
|
||||
gateways as optional later mesh in front of *our* service, not as sole
|
||||
control in front of vendor MCP.
|
||||
|
||||
### 3.4 Anchoring policy at Qonto (API / MCP)
|
||||
|
||||
**Not available** for the chosen custody model:
|
||||
|
||||
- Self-hosted API keys: no fine-grained scopes from Qonto.
|
||||
- Hosted OAuth MCP: write tools + credentials outside OpenBao = rejected
|
||||
by corporate access policy.
|
||||
|
||||
Defense-in-depth still belongs in our choke point even if Qonto later adds
|
||||
scopes.
|
||||
|
||||
---
|
||||
|
||||
## 4. Internal platform fit
|
||||
|
||||
| Component | Boundary (from INTENT/ADR) | Role for Qonto access |
|
||||
| --- | --- | --- |
|
||||
| **agent-harness** | Sole session credential holder / policy shell for *sessions*; not domain logic | Grant profile pointing at qonto-assistant only |
|
||||
| **llm-connect** | Multi-provider LLM adapters | Models only — not bank tools |
|
||||
| **flex-auth** | Authorization from identity claims | `finance.qonto.read` (etc.) |
|
||||
| **OpenBao + ops-warden** | Custody + routing | Existing `binky-qonto-api` lane |
|
||||
| **email-connect** | Purpose-built connector | Sibling pattern to copy |
|
||||
| **binky-control** | Company brain / AutonomyPolicy | Policy source + CostRunRate consumer |
|
||||
| **activity-core** | Schedule, not execute | When Finance Steward runs |
|
||||
|
||||
ADR-001 (agent-harness) implies: instances declare profiles; harness
|
||||
enforces. Domain bank rules must live **below** the harness so interactive
|
||||
IDE agents and unattended sessions share one policy.
|
||||
|
||||
---
|
||||
|
||||
## 5. Founder constraints captured in research session
|
||||
|
||||
1. Multiple coding agents and harnesses already in use; agent-harness +
|
||||
llm-connect exist.
|
||||
2. Desired policy: **no spending**, **no actions with volume cost**.
|
||||
3. Direct per-harness MCP wiring is suboptimal (policy drift).
|
||||
4. Preference to explore **qonto-proxy / governed API+MCP service**.
|
||||
5. Interest in known agentic patterns — research above.
|
||||
|
||||
---
|
||||
|
||||
## 6. Architectural recommendation (outcome)
|
||||
|
||||
See `specs/ArchitectureBlueprint.md` in full. Summary:
|
||||
|
||||
```text
|
||||
Many harnesses
|
||||
→ authenticate to qonto-assistant (MCP and/or REST)
|
||||
→ policy kernel (default-deny, no spend / no volume-cost)
|
||||
→ sole OpenBao consumer of tenants/.../qonto-api
|
||||
→ Qonto thirdparty API
|
||||
```
|
||||
|
||||
Phased delivery:
|
||||
|
||||
0. Custody + first REST pull (done in binky-control)
|
||||
1. Policy kernel + REST
|
||||
2. MCP surface for all harnesses
|
||||
3. flex-auth + rate limits
|
||||
4. Multi-tenant / productization
|
||||
|
||||
---
|
||||
|
||||
## 7. Open questions carried into work planning
|
||||
|
||||
1. Repo runtime stack (Python FastAPI vs other) — decide in first
|
||||
implementation workplan, not in research.
|
||||
2. Whether vendor MCP is ever an internal backend (default: **no**; prefer
|
||||
direct REST client).
|
||||
3. AppRole for assistant-only secret fetch vs OIDC role only.
|
||||
4. IBAN / PII redaction levels for agent channels.
|
||||
|
||||
---
|
||||
|
||||
## 8. Source index (non-exhaustive)
|
||||
|
||||
### Internal paths
|
||||
|
||||
- `binky-control/integrations/qonto-mcp.md`
|
||||
- `binky-control/integrations/qonto-governed-assistant/ArchitectureBlueprint.md` (origin copy)
|
||||
- `binky-control/finance/CostRunRate.md`, `finance/qonto-first-pull-2026-07-21.json`
|
||||
- `agent-harness/INTENT.md`, `docs/adr/ADR-001-agent-harness-architecture.md`
|
||||
- `llm-connect/ARCHITECTURE-LAYERS.md`
|
||||
- `flex-auth/INTENT.md`
|
||||
- ops-warden catalog entry `binky-qonto-api`; railiance-platform CCR-2026-0008
|
||||
|
||||
### External (retrieved 2026-07-21)
|
||||
|
||||
- Envoy AI Gateway MCP documentation (MCPRoute, toolSelector, authorization CEL)
|
||||
- Kong AI Gateway MCP Tool ACLs product notes
|
||||
- MCP gateway explainers (Speakeasy, Portkey, Permit, community)
|
||||
- MCP security best practices (token passthrough, proxy consent, tool-level scopes)
|
||||
- CSA / industry agentic MCP security maturity notes
|
||||
- Harness MCP server gateway compatibility notes
|
||||
- GitHub `qonto/qonto-mcp-server` README (env vars; maintained status warning for local server)
|
||||
499
specs/ArchitectureBlueprint.md
Normal file
499
specs/ArchitectureBlueprint.md
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
# Architecture Blueprint — Governed Qonto Assistant
|
||||
|
||||
> Status: blueprint v0.1 — 2026-07-21
|
||||
> Repo: **qonto-assistant** (canonical home).
|
||||
> Context: BINKY-WP-0005 finished (live OpenBao lane + first read-only pull).
|
||||
> Question: how do multiple coding agents / harnesses interact with Qonto
|
||||
> **conveniently and under one policy**, without wiring vendor MCP into each
|
||||
> client and accepting policy drift?
|
||||
> Related: `INTENT.md`, `research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md`,
|
||||
> binky-control `integrations/qonto-mcp.md`, AutonomyPolicy, agent-harness
|
||||
> ADR-001, ops-warden catalog `binky-qonto-api`, CCR-2026-0008.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem
|
||||
|
||||
We already can:
|
||||
|
||||
1. Fetch company Qonto credentials from OpenBao (`tenants/binky/qonto-api`).
|
||||
2. Call Qonto’s thirdparty REST API (proven: first pull, CostRunRate v2).
|
||||
3. Map those credentials into the self-hosted `qonto/qonto-mcp-server` env
|
||||
shape (`QONTO_API_KEY` / `QONTO_ORGANIZATION_ID`).
|
||||
|
||||
What we must **not** do as the long-term pattern:
|
||||
|
||||
- Install vendor MCP (or raw API keys) into Claude Desktop, Claude Code,
|
||||
Codex, Cursor, Grok, agent-harness, executor-worker, etc. **separately**.
|
||||
- Rely on each harness’s local tool allow-list as the only spend control.
|
||||
- Hope every client remembers “read-only forever.”
|
||||
|
||||
Why that fails:
|
||||
|
||||
| Failure mode | Effect |
|
||||
| --- | --- |
|
||||
| **Policy drift** | One harness allows a write tool; another forbids it. Same company key, different blast radius. |
|
||||
| **Credential sprawl** | Keys appear in N configs, Docker args, session env dumps. |
|
||||
| **Audit fragmentation** | No single log of “which agent did what to the bank.” |
|
||||
| **Qonto cannot help** | Business API keys are **not** scope-limited server-side (confirmed in design). There is no “read-only API key” or MCP-side spend ACL from Qonto. |
|
||||
| **Volume-cost tools** | Cards, invoicing, payment requests, transfers, and some “create” operations may incur fees or move money — must be blocked **before** the vendor sees them. |
|
||||
|
||||
**Founder policy for now (normative intent):**
|
||||
|
||||
> Agents may inspect balances, transactions, and statements for control-plane
|
||||
> and finance awareness. Agents must **not** initiate spending, transfers,
|
||||
> card operations, or any action with volume/transaction cost. Payments stay
|
||||
> Red lane (human in Qonto app).
|
||||
|
||||
That is stronger than “read-only MCP tools” in the abstract: it is a **business
|
||||
policy** that must be enforced at a choke point, not a client preference.
|
||||
|
||||
---
|
||||
|
||||
## 2. What industry patterns say
|
||||
|
||||
Research (2025–2026 MCP ecosystem) converges on the same shape your proxy
|
||||
intuition already points at.
|
||||
|
||||
### 2.1 MCP gateway / proxy as the control plane
|
||||
|
||||
Production patterns treat MCP **clients** (agent harnesses) as untrusted for
|
||||
tool policy, and put a **gateway** between them and tools:
|
||||
|
||||
- **Envoy AI Gateway MCPRoute** — multiplex backends, **toolSelector**
|
||||
allow/deny, OAuth on the gateway, upstream API-key injection so clients
|
||||
never hold vendor secrets, CEL rules on `tools/call` params.
|
||||
- **Kong AI Gateway MCP Tool ACLs** — default-deny, per-consumer tool
|
||||
subsets at the gateway (solves “all-or-nothing” MCP exposure).
|
||||
- **Speakeasy / Portkey / Permit MCP Gateway** framing — authn, RBAC, audit
|
||||
of every tool call, policy evaluated **on each call**, not only at
|
||||
connect time.
|
||||
- **Harness’s own MCP guidance** — gateways for centralized credentials,
|
||||
governance, audit, single endpoint vs N direct MCP connections.
|
||||
|
||||
Common lesson: **clients connect to one governed endpoint; the gateway owns
|
||||
credentials, tool inventory, and policy.**
|
||||
|
||||
### 2.2 MCP security best practices (protocol-level)
|
||||
|
||||
From MCP security guidance and industry write-ups:
|
||||
|
||||
| Practice | Application to Qonto |
|
||||
| --- | --- |
|
||||
| **No token passthrough** | Clients must not present the Qonto bank key. The assistant service is the only OAuth/API client to Qonto. |
|
||||
| **Tool-level scopes, not server-level** | “Access to Qonto MCP” is too coarse; allow `list_transactions`, deny `create_transfer`. |
|
||||
| **Default deny** | Unknown tools fail closed. |
|
||||
| **JIT escalation for high privilege** | Any future spend path is Yellow/Orange/Red with explicit re-authorization — never session-global “write mode.” |
|
||||
| **Confused-deputy / static client ID** | If the service ever OAuth-proxies, consent and audience binding matter; for API-key custody we avoid OAuth to Qonto entirely. |
|
||||
| **Audit end-to-end** | Actor (harness/agent id) + tool + args hash + decision + latency; no secrets in logs. |
|
||||
|
||||
### 2.3 Domain capability service (not “another harness”)
|
||||
|
||||
A second pattern, older than MCP: **BFF / capability API** for a domain
|
||||
(banking, email, CRM). Agents call a **narrow, policy-shaped interface**
|
||||
(“get runway snapshot”) rather than the full vendor surface. That is how
|
||||
email-connect already behaves for IMAP: consumer never holds IMAP secrets
|
||||
in the product path; it goes through custody + a purpose-built reader.
|
||||
|
||||
**llm-connect is the wrong home** for this: it is multi-provider **LLM**
|
||||
abstraction (adapters, routing, cost models). Putting bank tools there
|
||||
blurs the layer map (ARCHITECTURE-LAYERS.md) and couples finance policy to
|
||||
model routing.
|
||||
|
||||
**agent-harness is the wrong home for Qonto policy itself**, but the right
|
||||
home for *session* binding: harness ADR-001 already says the harness is the
|
||||
credential holder and policy enforcement point **for agent sessions**. That
|
||||
must not mean “each harness reimplements bank policy.” It means the harness
|
||||
only grants agents a **named tool profile** that talks to **already-governed**
|
||||
domain services. Domain policy lives **below** the harness.
|
||||
|
||||
### 2.4 Synthesis
|
||||
|
||||
| Pattern | Use for Qonto? |
|
||||
| --- | --- |
|
||||
| Wire vendor MCP into every harness | **No** — policy drift, key sprawl |
|
||||
| Generic MCP gateway only (Envoy/Kong in front of vendor MCP) | **Partial** — good transport + tool filter; weak on **semantic** spend rules and finance-shaped tools |
|
||||
| **Governed domain assistant** (our MCP + REST, policy inside) | **Yes — recommended** |
|
||||
| Flex-auth decisions on every call | **Yes** — who may call; lane/risk attributes |
|
||||
| OpenBao + ops-warden for bank key | **Yes** — already live; only the assistant fetches |
|
||||
|
||||
Your “qonto-proxy governed API & MCP service” is the right **class** of
|
||||
solution. Name it as a **capability assistant**, not only a dumb reverse
|
||||
proxy: it should *own* a safe tool surface, not only filter someone else’s.
|
||||
|
||||
---
|
||||
|
||||
## 3. Goals and non-goals
|
||||
|
||||
### Goals
|
||||
|
||||
1. **One policy** for “no spend / no volume-cost actions” across all agents.
|
||||
2. **One credential** path (existing OpenBao lane); harnesses never see bank keys.
|
||||
3. **Convenient agent UX**: natural “ask the finance assistant” via MCP and/or
|
||||
small REST helpers used by scripts and rhythm sessions.
|
||||
4. **Auditability**: every call attributable to agent/harness/session.
|
||||
5. **Fits NetKingdom**: flex-auth, OpenBao, ops-warden, agent-harness
|
||||
instances, activity-core schedules — no parallel IAM.
|
||||
6. **Dogfood-ready**: internal use is product evidence (DogfoodPolicy).
|
||||
|
||||
### Non-goals (v1)
|
||||
|
||||
- Replacing DATEV / StB / DUO accounting.
|
||||
- Automating payments, SEPA, card issuance, expense submission.
|
||||
- Hosted Qonto OAuth connector (`mcp.qonto.com`) as corporate custody path.
|
||||
- Making llm-connect a tool gateway.
|
||||
- Per-harness custom Qonto integrations.
|
||||
|
||||
---
|
||||
|
||||
## 4. Recommended architecture
|
||||
|
||||
### 4.1 One-liner
|
||||
|
||||
> **Build a small “Qonto Governed Assistant” service that is the only
|
||||
> component allowed to hold the bank API key and speak Qonto’s protocol.
|
||||
> Expose a **policy-shaped** MCP server and a thin REST API. All harnesses
|
||||
> are dumb clients of that service. Policy is code + tests in one place.**
|
||||
|
||||
### 4.2 Context diagram
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Agent clients (many) │
|
||||
│ Claude Code · Codex · Cursor · Grok · agent-harness sessions · scripts │
|
||||
└───────────────┬───────────────────────────────┬──────────────────────────┘
|
||||
│ MCP (streamable-HTTP) │ REST (JSON)
|
||||
│ tools/list · tools/call │ /v1/snapshot, /v1/txns, …
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Qonto Governed Assistant («qonto-assistant») │
|
||||
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────────────────┐ │
|
||||
│ │ Authn edge │→ │ Policy gate │→ │ Capability tools │ │
|
||||
│ │ (OIDC/mTLS │ │ (default-deny │ │ (finance-shaped, not 1:1 │ │
|
||||
│ │ flex-auth) │ │ tool+args+lane) │ │ vendor dump) │ │
|
||||
│ └─────────────┘ └────────┬─────────┘ └──────────────┬──────────────┘ │
|
||||
│ │ audit │ │
|
||||
│ ▼ ▼ │
|
||||
│ State Hub / logs Qonto client (REST) │
|
||||
│ (metadata only) Authorization: user:key │
|
||||
└───────────────────────────────────────────────┬──────────────────────────┘
|
||||
│ only this process
|
||||
▼
|
||||
OpenBao tenants/binky/qonto-api
|
||||
(via warden / AppRole — short TTL)
|
||||
│
|
||||
▼
|
||||
Qonto thirdparty API
|
||||
```
|
||||
|
||||
Optional later: put **Envoy AI Gateway** (or similar) **in front of**
|
||||
`qonto-assistant` for fleet-wide MCP aggregation with other domain assistants
|
||||
(email, issue-core, …). Do **not** put the gateway *directly* in front of
|
||||
vendor `qonto-mcp-server` as the sole control — semantic policy is thinner
|
||||
there.
|
||||
|
||||
### 4.3 Why a domain assistant beats “proxy the vendor MCP only”
|
||||
|
||||
| Approach | Pros | Cons |
|
||||
| --- | --- | --- |
|
||||
| **A. Gateway + vendor MCP** | Fast; toolSelector; industry-standard | Vendor tool names/args still drive the model; hard to encode “no volume cost”; vendor MCP is unmaintained / dual-use with write tools |
|
||||
| **B. Domain assistant (recommended)** | Safe tool catalog we design; semantic policy; dual MCP+REST; dogfood product surface | Build cost |
|
||||
| **C. Per-harness allow-lists** | Zero new service | Guaranteed drift; keys leak into clients |
|
||||
|
||||
**Hybrid that works:** implement **B**; if we still want vendor MCP for
|
||||
parity experiments, run it **only as an internal backend** behind the same
|
||||
policy gate (never published to harnesses). Prefer **direct REST client**
|
||||
inside the assistant (we already proved REST) — fewer moving parts than
|
||||
shelling out to unmaintained MCP.
|
||||
|
||||
### 4.4 Component responsibilities
|
||||
|
||||
| Component | Owns | Does not own |
|
||||
| --- | --- | --- |
|
||||
| **qonto-assistant** | Tool catalog, spend/volume policy, Qonto REST client, dual surfaces (MCP+REST), call audit | LLM routing, scheduling, identity issuance |
|
||||
| **OpenBao + ops-warden** | Custody path, rotation guide, high-risk agent boundary | Business rules about transfers |
|
||||
| **flex-auth** | “May actor X call capability `finance.qonto.read`?” | Bank credential material |
|
||||
| **agent-harness** | Session lifecycle; grants profile `finance-qonto-read`; injects *assistant* URL/token only | Bank policy implementation |
|
||||
| **llm-connect** | Which model answers | Tool execution |
|
||||
| **activity-core** | When Finance Steward runs | How bank is called |
|
||||
| **binky-control** | CostRunRate, queues, AutonomyPolicy, dogfood evidence | Runtime of the assistant |
|
||||
| **Qonto** | Bank of record | Our agent policy |
|
||||
|
||||
### 4.5 Surfaces (same policy, two protocols)
|
||||
|
||||
**MCP (agent-native)**
|
||||
|
||||
- Transport: streamable-HTTP (remote), optional stdio only for local dev
|
||||
with the **same** binary and policy module.
|
||||
- `tools/list` returns **only** approved tools (default-deny).
|
||||
- `tools/call` re-checks policy (tool id + arguments + actor claims) —
|
||||
connect-time allow-list alone is insufficient.
|
||||
|
||||
**REST (script / rhythm / tests)**
|
||||
|
||||
- Stable JSON for CostRunRate refresh, CI smoke, non-MCP agents.
|
||||
- Same policy middleware as MCP (shared library, one decision function).
|
||||
|
||||
Example tool catalog (v1 — illustrative):
|
||||
|
||||
| Tool / endpoint | Purpose | Policy |
|
||||
| --- | --- | --- |
|
||||
| `qonto_org_summary` | Org + account balances | Allow (Green/Blue) |
|
||||
| `qonto_list_transactions` | Filtered history | Allow; hard caps on page size; no export bulk to chat by default |
|
||||
| `qonto_cost_run_rate_hints` | Map debits to known CostRunRate rows | Allow |
|
||||
| `qonto_find_counterparties` | Search labels | Allow |
|
||||
| *(any transfer / payment / card / invoice create)* | — | **Deny always** (v1) |
|
||||
| *(vendor MCP write tools)* | — | Not registered |
|
||||
|
||||
### 4.6 Policy model (v1)
|
||||
|
||||
Encode as **code + declarative YAML**, tested in CI — not wiki prose alone.
|
||||
|
||||
```yaml
|
||||
# policy/qonto-v1.yaml (sketch)
|
||||
version: 1
|
||||
default: deny
|
||||
allow:
|
||||
- id: org_summary
|
||||
- id: list_transactions
|
||||
constraints:
|
||||
max_per_page: 100
|
||||
max_pages_per_call: 5
|
||||
- id: cost_run_rate_hints
|
||||
deny_classes:
|
||||
- spend # transfers, payouts, direct debits initiation
|
||||
- volume_cost # card ops, invoicing sends, paid features that bill per use
|
||||
- credential_exfil # tools that return raw API keys or full IBANs if avoidable
|
||||
lanes:
|
||||
green_blue: allow_set: [org_summary, list_transactions, cost_run_rate_hints]
|
||||
yellow_plus: same_as_green_blue # no spend even if human is "nearby"
|
||||
red: human_only_in_qonto_app
|
||||
```
|
||||
|
||||
**Semantic rules (beyond tool name):**
|
||||
|
||||
- Inspect arguments: reject if tool is “create_*”, amounts > 0 with side
|
||||
effects, or known write operation types.
|
||||
- Prefer **response shaping**: return IBAN last4, not full IBAN, in agent
|
||||
channels unless a higher assurance mode is granted later.
|
||||
- **Volume-cost**: deny anything that creates a fee-bearing Qonto operation
|
||||
(subscription change is also Red — out of band).
|
||||
|
||||
Map to AutonomyPolicy:
|
||||
|
||||
| Action class | Lane |
|
||||
| --- | --- |
|
||||
| Balance / txn read, CostRunRate update drafts | Green / Blue |
|
||||
| Recommend plan downgrade / keep Qonto | Green (recommend only) |
|
||||
| Change Qonto plan, create API key, transfer | Red |
|
||||
|
||||
### 4.7 Authentication and identity
|
||||
|
||||
```text
|
||||
Harness / human agent
|
||||
→ authenticates to qonto-assistant (OIDC netkingdom or mTLS workload id)
|
||||
→ flex-auth: finance.qonto.read (or finer)
|
||||
→ assistant fetches bank secret with its own AppRole / short-lived token
|
||||
→ Qonto sees only the company API identity (not the coding agent)
|
||||
```
|
||||
|
||||
- **Never** put `API_KEY` in harness env for general sessions.
|
||||
- Optional **AppRole** `agent-harness-binky-qonto` mirroring mail lane — but
|
||||
scoped so only `qonto-assistant` can read the path (not every agent
|
||||
process). Agents authenticate *to the assistant*, not to OpenBao for
|
||||
bank secrets.
|
||||
|
||||
### 4.8 Placement in the repo map (options)
|
||||
|
||||
| Option | Repo | When |
|
||||
| --- | --- | --- |
|
||||
| **A (preferred dogfood)** | New small service under Coulomb, e.g. `qonto-assistant` or generalize later to `finance-connect` | Clean boundary, productizable |
|
||||
| **B** | Module inside a future `domain-connectors` monorepo | If many thin connectors share skeleton |
|
||||
| **C** | Package under `binky-control` only | Too tenant-specific; resists reuse |
|
||||
|
||||
Start as **tenant-backed dogfood** with a clear INTENT that it may graduate
|
||||
to a multi-tenant “governed bank connector” offer. Keep binky-control as
|
||||
*consumer + policy source of business truth* (CostRunRate, AutonomyPolicy),
|
||||
not as the runtime host long-term.
|
||||
|
||||
### 4.9 Observability and evidence
|
||||
|
||||
Every call records (State Hub progress or dedicated audit log):
|
||||
|
||||
```yaml
|
||||
actor: agt-… / harness session id
|
||||
capability: finance.qonto.read
|
||||
tool: list_transactions
|
||||
decision: allow | deny
|
||||
deny_reason: volume_cost | unknown_tool | flex_auth | rate_limit
|
||||
latency_ms: …
|
||||
qonto_http_status: …
|
||||
# never: Authorization header, API_KEY, full account numbers if avoidable
|
||||
```
|
||||
|
||||
Finance Steward rhythm writes **metadata** into `finance/` only (same rule
|
||||
as first pull).
|
||||
|
||||
---
|
||||
|
||||
## 5. Alternatives considered
|
||||
|
||||
### 5.1 “Anchor policy at Qonto”
|
||||
|
||||
**Unavailable for our custody model.** Self-hosted MCP + API keys are not
|
||||
fine-scoped. Hosted OAuth MCP is the corporate **anti-pattern** we already
|
||||
rejected (credentials outside OpenBao, write tools bundled). Even with
|
||||
OAuth, spend prevention would still need our gateway for defense in depth.
|
||||
|
||||
### 5.2 Generic MCP gateway only
|
||||
|
||||
**Good complement, insufficient alone.** Use later for fleet aggregation.
|
||||
v1 needs **semantic** finance policy and a stable REST face for non-MCP
|
||||
clients — easier as our service.
|
||||
|
||||
### 5.3 agent-harness-only enforcement
|
||||
|
||||
Harness tool profiles remain necessary (don’t hand agents unrestricted
|
||||
network). But **bank policy must not live only there**: interactive Claude
|
||||
on a laptop would bypass harness profiles unless it also only sees the
|
||||
assistant endpoint. **Network/policy gravity toward the assistant.**
|
||||
|
||||
### 5.4 llm-connect as tool hub
|
||||
|
||||
Reject for layering reasons (LLM vs domain tools). Route models through
|
||||
llm-connect; route **bank actions** through qonto-assistant.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phased delivery
|
||||
|
||||
### Phase 0 — Already done (BINKY-WP-0005)
|
||||
|
||||
- [x] Custody lane live
|
||||
- [x] REST pull proven
|
||||
- [x] CostRunRate TBC resolved for desk + plan
|
||||
- [x] Catalog + CCR active
|
||||
|
||||
### Phase 1 — Policy kernel + REST (minimum useful product)
|
||||
|
||||
1. Service skeleton with shared `decide(tool, args, claims) -> Allow|Deny`.
|
||||
2. REST: `GET /v1/accounts`, `GET /v1/transactions`, `GET /v1/snapshot`.
|
||||
3. Hard deny list for any write/spend path (even if not implemented).
|
||||
4. OpenBao fetch only inside service (AppRole or OIDC role).
|
||||
5. Smoke: CI uses mock Qonto; manual: live read against dogfood account.
|
||||
6. Script replaces ad-hoc first-pull for CostRunRate refresh.
|
||||
|
||||
### Phase 2 — MCP surface for all harnesses
|
||||
|
||||
1. Streamable-HTTP MCP on the same decision function.
|
||||
2. Document **one** client config snippet for Claude/Codex/Cursor/Grok:
|
||||
URL + OIDC/workload auth — **no bank secrets**.
|
||||
3. agent-harness tool profile `finance-qonto-read` → assistant only.
|
||||
4. Audit events to State Hub.
|
||||
|
||||
### Phase 3 — Flex-auth + fleet
|
||||
|
||||
1. flex-auth resource `finance.qonto.read` (+ later `.export`).
|
||||
2. Rate limits, concurrency caps, optional response redaction modes.
|
||||
3. Optional Envoy/gateway in front for multi-assistant mesh.
|
||||
|
||||
### Phase 4 — Productization (dogfood → offer)
|
||||
|
||||
1. Multi-tenant path layout `tenants/<slug>/qonto-api`.
|
||||
2. Policy packs per autonomy lane.
|
||||
3. OfferCatalog entry: “Governed bank assistant for agent fleets.”
|
||||
|
||||
---
|
||||
|
||||
## 7. Harness integration pattern (the consistency trick)
|
||||
|
||||
**Wrong:**
|
||||
|
||||
```text
|
||||
each harness → OpenBao qonto key → vendor MCP (local allow-list hope)
|
||||
```
|
||||
|
||||
**Right:**
|
||||
|
||||
```text
|
||||
each harness → auth to qonto-assistant → policy → Qonto
|
||||
↑
|
||||
single tool catalog + single deny rules
|
||||
```
|
||||
|
||||
Checklist for any new harness:
|
||||
|
||||
1. Does **not** get `binky-qonto-api` bank secret.
|
||||
2. Gets only assistant base URL + client credentials for `finance.qonto.read`.
|
||||
3. Tool profile names assistant tools, not vendor tool names.
|
||||
4. Offline/dev: point at assistant mock, not a copy of production key.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risk register (architecture-level)
|
||||
|
||||
| Risk | Mitigation |
|
||||
| --- | --- |
|
||||
| Assistant is a high-value target | High-risk path already in agent-high-risk-boundary; no raw stream to coding agents; short-lived OpenBao tokens; network policy |
|
||||
| Model jailbreak asks for transfer | Tool not registered + deny classes; never implement spend in v1 |
|
||||
| Vendor adds new MCP write tools | We do not auto-mirror vendor catalog; allow-list is ours |
|
||||
| Bypass via direct thirdparty from laptop | Platform policy + founder discipline; optional egress controls later; secrets not on laptops |
|
||||
| Over-filtering legitimate finance work | Explicit allow tools + CostRunRate helpers; expand by policy PR with tests |
|
||||
|
||||
---
|
||||
|
||||
## 9. Decision requests (for later DEC / workplan)
|
||||
|
||||
1. **Adopt domain-assistant architecture (B)** for Qonto (this blueprint).
|
||||
2. **Repo home:** new `qonto-assistant` vs wait for generic `finance-connect`.
|
||||
3. **v1 policy freeze:** no spend / no volume-cost tools — hard deny.
|
||||
4. **MCP transport:** remote streamable-HTTP only for production clients.
|
||||
5. **Whether** to ever run vendor MCP as internal backend (default: **no**).
|
||||
|
||||
Suggested workplan slug: `BINKY-WP-0006` or a Coulomb-side
|
||||
`QONTO-WP-0001` once the repo exists.
|
||||
|
||||
---
|
||||
|
||||
## 10. References
|
||||
|
||||
### Internal
|
||||
|
||||
- `integrations/qonto-mcp.md` — variant decision, custody, first-pull results
|
||||
- `finance/CostRunRate.md` — consumer of read path
|
||||
- `AutonomyPolicy.md` — Green…Red lanes; spending defaults
|
||||
- `agent-harness` INTENT + ADR-001 — single runtime; instances declarative
|
||||
- `llm-connect` ARCHITECTURE-LAYERS — LLM only, not domain tools
|
||||
- `flex-auth` INTENT — authorization registry
|
||||
- ops-warden catalog `binky-qonto-api`, CCR-2026-0008
|
||||
|
||||
### External patterns (research snapshot 2026-07)
|
||||
|
||||
- Envoy AI Gateway MCPRoute: tool filtering, upstream key injection, OAuth,
|
||||
CEL on MCP params
|
||||
- Kong AI Gateway MCP Tool ACLs: default-deny per-consumer tool grants
|
||||
- MCP gateway product literature (Portkey, Speakeasy, Permit): central
|
||||
policy + audit for multi-client agents
|
||||
- MCP security guidance: no token passthrough; tool-level scopes; proxy
|
||||
consent / confused-deputy awareness
|
||||
- CSA / industry agentic MCP guides: inventory of servers; JIT escalation
|
||||
for high privilege
|
||||
|
||||
---
|
||||
|
||||
## 11. Summary recommendation
|
||||
|
||||
1. **Do not** wire Qonto MCP into each harness.
|
||||
2. **Do** build a **Qonto Governed Assistant** with:
|
||||
- sole possession of bank credentials,
|
||||
- default-deny tool catalog,
|
||||
- **no spend / no volume-cost** policy as code,
|
||||
- MCP + REST for all clients,
|
||||
- flex-auth + OpenBao + audit.
|
||||
3. Treat generic MCP gateways as a **later mesh layer**, not a substitute
|
||||
for domain policy.
|
||||
4. Align agent-harness so it only ever grants access to that assistant —
|
||||
preserving ADR-001 without re-implementing bank rules per runtime.
|
||||
|
||||
This matches both industry MCP-gateway practice and your existing
|
||||
platform boundaries (custody, flex-auth, harness instances, dogfood).
|
||||
67
workplans/QONTO-WP-0001-statehub-bootstrap.md
Normal file
67
workplans/QONTO-WP-0001-statehub-bootstrap.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
id: QONTO-WP-0001
|
||||
type: workplan
|
||||
title: "Bootstrap State Hub integration"
|
||||
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: "b2faddb4-2fdc-4e2a-b2df-9b6dd38faa86"
|
||||
---
|
||||
|
||||
# Bootstrap State Hub integration
|
||||
|
||||
Register and prime `qonto-assistant` for fleet agents: identity files, first
|
||||
workplans, hub consistency.
|
||||
|
||||
## Review Generated Integration Files
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0001-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "aab19eda-00ff-4e43-91c6-7272bd0422e5"
|
||||
```
|
||||
|
||||
Review `INTENT.md`, `SCOPE.md`, `AGENTS.md`, and `.custodian-brief.md`.
|
||||
Replace generated placeholders with repo-specific facts where needed.
|
||||
|
||||
**Done 2026-07-21:** INTENT authored from governed-assistant design; SCOPE
|
||||
refined; README points at specs/research; register generated AGENTS.md + brief.
|
||||
|
||||
## Verify Local Developer Workflow
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0001-T02
|
||||
status: wait
|
||||
priority: medium
|
||||
state_hub_task_id: "360f399a-36f8-44e6-af8a-55fb0d5732a3"
|
||||
```
|
||||
|
||||
Identify the repo's install, test, lint, build, and run commands. Add or refine
|
||||
those commands in the agent instructions so future coding sessions can verify
|
||||
changes confidently.
|
||||
|
||||
**Blocked on** runtime stack choice in QONTO-WP-0002-T01. Revisit after that task.
|
||||
|
||||
## Seed First Real Workplan
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0001-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "ee9a8125-ff00-4640-80bd-81567722bba6"
|
||||
```
|
||||
|
||||
Create the first implementation workplan for the repository's most important
|
||||
next change. After workplan file updates, run:
|
||||
|
||||
```bash
|
||||
statehub fix-consistency
|
||||
```
|
||||
|
||||
**Done 2026-07-21:** `workplans/QONTO-WP-0002-policy-kernel-and-rest.md` (Phase 1
|
||||
from ArchitectureBlueprint).
|
||||
145
workplans/QONTO-WP-0002-policy-kernel-and-rest.md
Normal file
145
workplans/QONTO-WP-0002-policy-kernel-and-rest.md
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
id: QONTO-WP-0002
|
||||
type: workplan
|
||||
title: "Phase 1 — policy kernel and read-only REST"
|
||||
domain: infotech
|
||||
repo: qonto-assistant
|
||||
status: ready
|
||||
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 shared `decide(tool, args, claims) → Allow|Deny` policy kernel and a
|
||||
minimal REST surface for org/accounts/transactions/snapshot. No MCP yet (Phase 2).
|
||||
No spend or volume-cost tools — default deny.
|
||||
|
||||
**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: todo
|
||||
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 (policy, qonto client, api, audit).
|
||||
|
||||
## Task: Policy kernel — default-deny no-spend / no-volume-cost
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0002-T02
|
||||
status: todo
|
||||
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)
|
||||
- hard deny classes: `spend`, `volume_cost`, `credential_exfil`
|
||||
- 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: todo
|
||||
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
|
||||
- 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.
|
||||
|
||||
Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
||||
|
||||
## Task: REST API surface
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0002-T04
|
||||
status: todo
|
||||
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` | cost/run-rate oriented summary |
|
||||
|
||||
Done when: OpenAPI or documented curl examples; integration test with mock
|
||||
client; deny paths return 403 with stable error code.
|
||||
|
||||
## Task: Audit metadata (no secrets)
|
||||
|
||||
```task
|
||||
id: QONTO-WP-0002-T05
|
||||
status: todo
|
||||
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. Never secret fields. Prefer
|
||||
stdout JSON + optional State Hub progress for dogfood runs.
|
||||
|
||||
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: todo
|
||||
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: todo
|
||||
priority: low
|
||||
state_hub_task_id: "9b99ba05-99f2-4624-9044-89bf37055434"
|
||||
```
|
||||
|
||||
Mark workplan finished when T01–T06 done; note Phase 2 seed (MCP surface for
|
||||
all harnesses) in closure. Run `statehub fix-consistency`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue