From e1ab23f83e1ddebac24fa73dca92a91d89af8eb8 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 23:32:24 +0200 Subject: [PATCH] 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). --- .gitignore | 6 + AGENTS.md | 206 ++++++++ INTENT.md | 135 +++++ README.md | 24 +- SCOPE.md | 55 ++ WORK-RECORDS.md | 22 + ...p-gateway-and-governed-domain-assistant.md | 172 ++++++ specs/ArchitectureBlueprint.md | 499 ++++++++++++++++++ workplans/QONTO-WP-0001-statehub-bootstrap.md | 67 +++ .../QONTO-WP-0002-policy-kernel-and-rest.md | 145 +++++ 10 files changed, 1330 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 INTENT.md create mode 100644 SCOPE.md create mode 100644 WORK-RECORDS.md create mode 100644 research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md create mode 100644 specs/ArchitectureBlueprint.md create mode 100644 workplans/QONTO-WP-0001-statehub-bootstrap.md create mode 100644 workplans/QONTO-WP-0002-policy-kernel-and-rest.md diff --git a/.gitignore b/.gitignore index ab3e8ce..73caa0b 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b4a3153 --- /dev/null +++ b/AGENTS.md @@ -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//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": "", + "task_id": "" + }' +``` + +Omit `workplan_id` / `task_id` when not applicable. + +### Update task status + +```bash +curl -s -X PATCH "http://127.0.0.1:8000/tasks/" \ + -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/" \ + -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-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-.md` + +**Archived location:** finished workplans may move to +`workplans/archived/YYMMDD-QONTO-WP-NNNN-.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: "" # 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: "" # 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. diff --git a/INTENT.md b/INTENT.md new file mode 100644 index 0000000..9cc3aab --- /dev/null +++ b/INTENT.md @@ -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//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 diff --git a/README.md b/README.md index 3775124..6bb080f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # qonto-assistant -A policy governed qonto domain API and MCP assistant for binky-control. \ No newline at end of file +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 diff --git a/SCOPE.md b/SCOPE.md new file mode 100644 index 0000000..b9affb8 --- /dev/null +++ b/SCOPE.md @@ -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//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 diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md new file mode 100644 index 0000000..cf51cae --- /dev/null +++ b/WORK-RECORDS.md @@ -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 | diff --git a/research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md b/research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md new file mode 100644 index 0000000..8eb156f --- /dev/null +++ b/research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md @@ -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: :` | +| 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) diff --git a/specs/ArchitectureBlueprint.md b/specs/ArchitectureBlueprint.md new file mode 100644 index 0000000..df0236a --- /dev/null +++ b/specs/ArchitectureBlueprint.md @@ -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//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). diff --git a/workplans/QONTO-WP-0001-statehub-bootstrap.md b/workplans/QONTO-WP-0001-statehub-bootstrap.md new file mode 100644 index 0000000..20ff32b --- /dev/null +++ b/workplans/QONTO-WP-0001-statehub-bootstrap.md @@ -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). diff --git a/workplans/QONTO-WP-0002-policy-kernel-and-rest.md b/workplans/QONTO-WP-0002-policy-kernel-and-rest.md new file mode 100644 index 0000000..6ce2f02 --- /dev/null +++ b/workplans/QONTO-WP-0002-policy-kernel-and-rest.md @@ -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`.