Seeded intent and initial workplan
This commit is contained in:
parent
e1ab23f83e
commit
67a5b01e08
3 changed files with 263 additions and 115 deletions
|
|
@ -85,7 +85,7 @@ them with operational bank visibility.
|
||||||
assistant alone authenticates *to Qonto*.
|
assistant alone authenticates *to Qonto*.
|
||||||
4. **Semantic policy, not vendor dump** — we design a safe catalog; we do
|
4. **Semantic policy, not vendor dump** — we design a safe catalog; we do
|
||||||
not auto-mirror vendor write tools.
|
not auto-mirror vendor write tools.
|
||||||
5. **Platform fit** — flex-auth, OpenBao, ops-warden, State Hub audit; no
|
5. **Platform fit** — flex-auth, OpenBao, ops-warden, State Hub evidence; no
|
||||||
parallel IAM.
|
parallel IAM.
|
||||||
6. **Autonomy lanes** — map to binky-control AutonomyPolicy: reads Green/Blue;
|
6. **Autonomy lanes** — map to binky-control AutonomyPolicy: reads Green/Blue;
|
||||||
plan change / transfers / API key minting remain Red.
|
plan change / transfers / API key minting remain Red.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Architecture Blueprint — Governed Qonto Assistant
|
# Architecture Blueprint — Governed Qonto Assistant
|
||||||
|
|
||||||
> Status: blueprint v0.1 — 2026-07-21
|
> Status: blueprint v0.2 — 2026-07-21 (revised after repo review)
|
||||||
> Repo: **qonto-assistant** (canonical home).
|
> Repo: **qonto-assistant** (canonical home).
|
||||||
> Context: BINKY-WP-0005 finished (live OpenBao lane + first read-only pull).
|
> Context: BINKY-WP-0005 finished (live OpenBao lane + first read-only pull).
|
||||||
> Question: how do multiple coding agents / harnesses interact with Qonto
|
> Question: how do multiple coding agents / harnesses interact with Qonto
|
||||||
|
|
@ -159,31 +159,42 @@ proxy: it should *own* a safe tool surface, not only filter someone else’s.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
┌──────────────────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Agent clients (many) │
|
│ Agent clients │
|
||||||
│ Claude Code · Codex · Cursor · Grok · agent-harness sessions · scripts │
|
│ Claude Code · Codex · Cursor · Grok · agent-harness sessions · scripts │
|
||||||
└───────────────┬───────────────────────────────┬──────────────────────────┘
|
└───────────────┬───────────────────────────────┬──────────────────────────┘
|
||||||
│ MCP (streamable-HTTP) │ REST (JSON)
|
│ MCP (streamable-HTTP) │ REST (JSON)
|
||||||
│ tools/list · tools/call │ /v1/snapshot, /v1/txns, …
|
|
||||||
▼ ▼
|
▼ ▼
|
||||||
┌──────────────────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Qonto Governed Assistant («qonto-assistant») │
|
│ Qonto Governed Assistant («qonto-assistant») │
|
||||||
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────────────────┐ │
|
│ │
|
||||||
│ │ Authn edge │→ │ Policy gate │→ │ Capability tools │ │
|
│ Authn/Authz edge │
|
||||||
│ │ (OIDC/mTLS │ │ (default-deny │ │ (finance-shaped, not 1:1 │ │
|
│ OIDC / workload id / mTLS + optional flex-auth │
|
||||||
│ │ flex-auth) │ │ tool+args+lane) │ │ vendor dump) │ │
|
│ │ │
|
||||||
│ └─────────────┘ └────────┬─────────┘ └──────────────┬──────────────┘ │
|
│ ▼ │
|
||||||
│ │ audit │ │
|
│ Protocol adapters │
|
||||||
│ ▼ ▼ │
|
│ MCP adapter REST adapter │
|
||||||
│ State Hub / logs Qonto client (REST) │
|
│ │ │ │
|
||||||
│ (metadata only) Authorization: user:key │
|
│ └──────────┬────────┘ │
|
||||||
└───────────────────────────────────────────────┬──────────────────────────┘
|
│ ▼ │
|
||||||
│ only this process
|
│ Capability service core │
|
||||||
▼
|
│ canonical capability ids + response contracts │
|
||||||
OpenBao tenants/binky/qonto-api
|
│ │ │
|
||||||
(via warden / AppRole — short TTL)
|
│ ▼ │
|
||||||
│
|
│ Policy engine │
|
||||||
▼
|
│ default-deny, tenant scope, arg constraints, data class rules │
|
||||||
Qonto thirdparty API
|
│ │ │
|
||||||
|
│ ┌─────┴──────────┐ │
|
||||||
|
│ ▼ ▼ │
|
||||||
|
│ Qonto adapter Audit emitter │
|
||||||
|
│ REST client structured events │
|
||||||
|
│ │ │ │
|
||||||
|
└──────┼────────────────┼──────────────────────────────────────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
OpenBao tenants/<tenant>/qonto-api stdout / log sink / metrics
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Qonto thirdparty API
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional later: put **Envoy AI Gateway** (or similar) **in front of**
|
Optional later: put **Envoy AI Gateway** (or similar) **in front of**
|
||||||
|
|
@ -219,65 +230,102 @@ shelling out to unmaintained MCP.
|
||||||
| **binky-control** | CostRunRate, queues, AutonomyPolicy, dogfood evidence | Runtime of the assistant |
|
| **binky-control** | CostRunRate, queues, AutonomyPolicy, dogfood evidence | Runtime of the assistant |
|
||||||
| **Qonto** | Bank of record | Our agent policy |
|
| **Qonto** | Bank of record | Our agent policy |
|
||||||
|
|
||||||
### 4.5 Surfaces (same policy, two protocols)
|
### 4.5 Protocol adapters over one service core
|
||||||
|
|
||||||
**MCP (agent-native)**
|
The critical implementation rule is:
|
||||||
|
|
||||||
- Transport: streamable-HTTP (remote), optional stdio only for local dev
|
> REST routes and MCP tools are only transport adapters. They must translate
|
||||||
with the **same** binary and policy module.
|
> into the same internal `CapabilityRequest` model before policy runs.
|
||||||
- `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)**
|
That avoids the most likely drift bug in this repo: REST enforcing one set of
|
||||||
|
rules while MCP grows a slightly different set later.
|
||||||
|
|
||||||
- Stable JSON for CostRunRate refresh, CI smoke, non-MCP agents.
|
Canonical request shape:
|
||||||
- Same policy middleware as MCP (shared library, one decision function).
|
|
||||||
|
|
||||||
Example tool catalog (v1 — illustrative):
|
```text
|
||||||
|
CapabilityRequest
|
||||||
|
capability_id
|
||||||
|
tenant_id
|
||||||
|
actor_claims
|
||||||
|
resource_scope
|
||||||
|
request_args
|
||||||
|
protocol # rest | mcp
|
||||||
|
```
|
||||||
|
|
||||||
| Tool / endpoint | Purpose | Policy |
|
Recommended v1 capability map:
|
||||||
| --- | --- | --- |
|
|
||||||
| `qonto_org_summary` | Org + account balances | Allow (Green/Blue) |
|
| Internal capability id | REST surface | MCP surface | Notes |
|
||||||
| `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 |
|
| `org_summary` | `GET /v1/accounts` | `qonto_org_summary` | Core balances/org read |
|
||||||
| `qonto_find_counterparties` | Search labels | Allow |
|
| `list_transactions` | `GET /v1/transactions` | `qonto_list_transactions` | Capped, filtered history |
|
||||||
| *(any transfer / payment / card / invoice create)* | — | **Deny always** (v1) |
|
| `cost_run_rate_hints` | used by `GET /v1/snapshot` | `qonto_cost_run_rate_hints` (Phase 2) | Normalized finance hints, not raw export |
|
||||||
| *(vendor MCP write tools)* | — | Not registered |
|
| `snapshot_bundle` | `GET /v1/snapshot` | optional later | Composite read; not a separate privilege if it only orchestrates allowed reads |
|
||||||
|
| `find_counterparties` | defer | defer | Candidate future read capability, not Phase 1 MVP |
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `snapshot_bundle` is an orchestrator over already-allowed reads, not a way
|
||||||
|
to bypass policy with a "bigger" internal export.
|
||||||
|
- REST and MCP names may differ, but both must map to the same
|
||||||
|
`capability_id`, deny reasons, and response redaction rules.
|
||||||
|
- Vendor payloads should be normalized into repo-owned response schemas; do
|
||||||
|
not stream raw Qonto JSON into agent contexts by default.
|
||||||
|
|
||||||
### 4.6 Policy model (v1)
|
### 4.6 Policy model (v1)
|
||||||
|
|
||||||
Encode as **code + declarative YAML**, tested in CI — not wiki prose alone.
|
Encode as **code + declarative YAML**, tested in CI — not wiki prose alone.
|
||||||
|
Policy should evaluate this tuple:
|
||||||
|
|
||||||
|
```text
|
||||||
|
(capability_id, actor_claims, tenant_id, resource_scope, request_args, response_class)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sketch:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# policy/qonto-v1.yaml (sketch)
|
# policy/qonto-v1.yaml (sketch)
|
||||||
version: 1
|
version: 1
|
||||||
default: deny
|
default: deny
|
||||||
allow:
|
capabilities:
|
||||||
- id: org_summary
|
org_summary:
|
||||||
- id: list_transactions
|
lanes: [green, blue]
|
||||||
|
list_transactions:
|
||||||
|
lanes: [green, blue]
|
||||||
constraints:
|
constraints:
|
||||||
max_per_page: 100
|
max_per_page: 100
|
||||||
max_pages_per_call: 5
|
max_pages_per_call: 5
|
||||||
- id: cost_run_rate_hints
|
max_window_days: 93
|
||||||
|
cost_run_rate_hints:
|
||||||
|
lanes: [green, blue]
|
||||||
|
snapshot_bundle:
|
||||||
|
compose_only: [org_summary, cost_run_rate_hints]
|
||||||
deny_classes:
|
deny_classes:
|
||||||
- spend # transfers, payouts, direct debits initiation
|
spend:
|
||||||
- volume_cost # card ops, invoicing sends, paid features that bill per use
|
match_prefixes: [create_, issue_, transfer_, payout_]
|
||||||
- credential_exfil # tools that return raw API keys or full IBANs if avoidable
|
volume_cost:
|
||||||
|
match_tags: [card_operation, invoice_send, payment_link, subscription_change]
|
||||||
|
credential_exfil:
|
||||||
|
response_fields: [api_key, authorization_header, full_iban]
|
||||||
lanes:
|
lanes:
|
||||||
green_blue: allow_set: [org_summary, list_transactions, cost_run_rate_hints]
|
green_blue:
|
||||||
yellow_plus: same_as_green_blue # no spend even if human is "nearby"
|
allow_set: [org_summary, list_transactions, cost_run_rate_hints, snapshot_bundle]
|
||||||
red: human_only_in_qonto_app
|
yellow_plus:
|
||||||
|
same_as: green_blue
|
||||||
|
red:
|
||||||
|
human_only_in_qonto_app: true
|
||||||
```
|
```
|
||||||
|
|
||||||
**Semantic rules (beyond tool name):**
|
Semantic rules beyond tool name:
|
||||||
|
|
||||||
- Inspect arguments: reject if tool is “create_*”, amounts > 0 with side
|
- Reject unknown capability ids before touching Qonto.
|
||||||
effects, or known write operation types.
|
- Enforce tenant binding before resource lookup; callers do not choose an
|
||||||
- Prefer **response shaping**: return IBAN last4, not full IBAN, in agent
|
arbitrary OpenBao path or tenant id.
|
||||||
channels unless a higher assurance mode is granted later.
|
- Inspect arguments for suspicious write semantics even if a route/tool is
|
||||||
- **Volume-cost**: deny anything that creates a fee-bearing Qonto operation
|
mis-wired later.
|
||||||
(subscription change is also Red — out of band).
|
- Treat response shaping as policy, not presentation polish. Redaction rules
|
||||||
|
should be versioned and testable like allow/deny rules.
|
||||||
|
- Emit stable deny reasons such as `unknown_capability`, `tenant_scope`,
|
||||||
|
`arg_constraint`, `volume_cost`, `credential_exfil`, and `authz_denied`.
|
||||||
|
|
||||||
Map to AutonomyPolicy:
|
Map to AutonomyPolicy:
|
||||||
|
|
||||||
|
|
@ -289,21 +337,117 @@ Map to AutonomyPolicy:
|
||||||
|
|
||||||
### 4.7 Authentication and identity
|
### 4.7 Authentication and identity
|
||||||
|
|
||||||
|
Separate three identities clearly:
|
||||||
|
|
||||||
|
1. **Caller identity**: human or workload calling REST/MCP.
|
||||||
|
2. **Assistant service identity**: the only identity allowed to read
|
||||||
|
`tenants/<tenant>/qonto-api`.
|
||||||
|
3. **Qonto upstream identity**: the company credential presented to Qonto.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Harness / human agent
|
Caller
|
||||||
→ authenticates to qonto-assistant (OIDC netkingdom or mTLS workload id)
|
-> authenticates to qonto-assistant (OIDC workload identity, mTLS, or similar)
|
||||||
→ flex-auth: finance.qonto.read (or finer)
|
-> assistant checks claims + optional flex-auth grant (finance.qonto.read)
|
||||||
→ assistant fetches bank secret with its own AppRole / short-lived token
|
-> assistant resolves tenant from claims/policy
|
||||||
→ Qonto sees only the company API identity (not the coding agent)
|
-> assistant fetches secret using its own runtime role
|
||||||
|
-> Qonto sees only the tenant's API identity
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Never** put `API_KEY` in harness env for general sessions.
|
Rules:
|
||||||
- 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)
|
- **Never** put bank credentials in harness env for general sessions.
|
||||||
|
- Do **not** reuse an `agent-harness-*` OpenBao role for assistant secret
|
||||||
|
fetch. Use a dedicated assistant runtime identity such as
|
||||||
|
`qonto-assistant-runtime`.
|
||||||
|
- Even in single-tenant dogfood, keep `tenant_id` explicit in claims and audit
|
||||||
|
events so productization does not require reworking the core contract.
|
||||||
|
|
||||||
|
### 4.8 Secret and upstream session lifecycle
|
||||||
|
|
||||||
|
The blueprint needs an explicit secret lifecycle because this service is the
|
||||||
|
sole credential choke point.
|
||||||
|
|
||||||
|
- OpenBao read happens inside the service only.
|
||||||
|
- Secrets may be cached **in memory only** with a short TTL to avoid fetching
|
||||||
|
on every request.
|
||||||
|
- Cache invalidation should occur on startup, TTL expiry, and upstream 401/403.
|
||||||
|
- No secret material is written to disk, progress logs, traces, or chat output.
|
||||||
|
- Local developer mode may use env inject for mocks/tests, but production path
|
||||||
|
remains OpenBao-first.
|
||||||
|
|
||||||
|
This keeps rotation and incident response tractable without coupling every
|
||||||
|
request to a secret store round-trip.
|
||||||
|
|
||||||
|
### 4.9 Data handling and response shaping
|
||||||
|
|
||||||
|
The current blueprint mentions redaction, but this needs to be a first-class
|
||||||
|
contract because the main exfil path is not only "the key" but over-sharing
|
||||||
|
financial data into agent chat contexts.
|
||||||
|
|
||||||
|
| Data class | Examples | Default handling |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `secret` | API key, Authorization header, OpenBao token | Never return, never log |
|
||||||
|
| `sensitive_financial` | full IBAN, account owner identifiers, raw vendor refs | Redact or truncate by default |
|
||||||
|
| `operational_summary` | balances, normalized counterparties, capped txn summaries | Return when capability allows |
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
|
||||||
|
- Prefer normalized summary DTOs over raw vendor objects.
|
||||||
|
- Default to IBAN last4 or account alias, not full identifiers.
|
||||||
|
- Bulk export, statement download, and raw vendor dumps are out of scope for v1.
|
||||||
|
- If a future higher-assurance mode needs fuller data, it should be a separate
|
||||||
|
capability with separate policy and audit semantics, not a flag hidden inside
|
||||||
|
an existing read.
|
||||||
|
|
||||||
|
### 4.10 Observability and audit
|
||||||
|
|
||||||
|
Per-request audit should not depend on State Hub progress writes. State Hub is
|
||||||
|
appropriate for work/progress records, not as the hot-path sink for every bank
|
||||||
|
read.
|
||||||
|
|
||||||
|
Recommended split:
|
||||||
|
|
||||||
|
- **Hot path**: structured stdout/file/OTLP audit events for every request.
|
||||||
|
- **Cold path**: State Hub progress notes or operator summaries for notable
|
||||||
|
sessions, incidents, or rollout evidence.
|
||||||
|
|
||||||
|
Suggested audit envelope:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
request_id: req-...
|
||||||
|
timestamp: ...
|
||||||
|
actor: agt-... / workload id
|
||||||
|
tenant_id: binky
|
||||||
|
capability: list_transactions
|
||||||
|
protocol: rest | mcp
|
||||||
|
decision: allow | deny
|
||||||
|
deny_reason: volume_cost | unknown_capability | tenant_scope | authz_denied
|
||||||
|
policy_version: 1
|
||||||
|
latency_ms: ...
|
||||||
|
qonto_http_status: ...
|
||||||
|
result_count: ...
|
||||||
|
# never: Authorization header, API_KEY, OpenBao token, full IBAN by default
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.11 Operational guardrails (v1, not "later if we remember")
|
||||||
|
|
||||||
|
Basic safety and reliability controls belong in Phase 1 because one noisy
|
||||||
|
agent session can otherwise degrade the single bank integration for everyone.
|
||||||
|
|
||||||
|
| Guardrail | Minimum requirement |
|
||||||
|
| --- | --- |
|
||||||
|
| Upstream timeout | bounded per request; fail fast and explicitly |
|
||||||
|
| Pagination | hard caps in policy, not caller preference |
|
||||||
|
| Concurrency | bounded per actor/tenant to avoid Qonto burst abuse |
|
||||||
|
| Rate limiting | basic per-actor or per-token limit from first deploy |
|
||||||
|
| Retry policy | conservative; no blind retries on ambiguous write-like failures |
|
||||||
|
| Persistence | no raw Qonto payload store in v1; ephemeral in-memory only |
|
||||||
|
| Failure mode | fail closed on policy/authn/authz uncertainty |
|
||||||
|
|
||||||
|
These controls move from "nice operational follow-up" to "architecture
|
||||||
|
requirement" because the repo's entire point is governed centralization.
|
||||||
|
|
||||||
|
### 4.12 Placement in the repo map (options)
|
||||||
|
|
||||||
| Option | Repo | When |
|
| Option | Repo | When |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|
@ -316,24 +460,6 @@ to a multi-tenant “governed bank connector” offer. Keep binky-control as
|
||||||
*consumer + policy source of business truth* (CostRunRate, AutonomyPolicy),
|
*consumer + policy source of business truth* (CostRunRate, AutonomyPolicy),
|
||||||
not as the runtime host long-term.
|
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. Alternatives considered
|
||||||
|
|
@ -376,25 +502,31 @@ llm-connect; route **bank actions** through qonto-assistant.
|
||||||
|
|
||||||
### Phase 1 — Policy kernel + REST (minimum useful product)
|
### Phase 1 — Policy kernel + REST (minimum useful product)
|
||||||
|
|
||||||
1. Service skeleton with shared `decide(tool, args, claims) -> Allow|Deny`.
|
1. Service skeleton with a protocol-neutral capability core and shared
|
||||||
2. REST: `GET /v1/accounts`, `GET /v1/transactions`, `GET /v1/snapshot`.
|
`decide(request, claims) -> Allow|Deny`.
|
||||||
|
2. REST adapters for `GET /v1/accounts`, `GET /v1/transactions`,
|
||||||
|
`GET /v1/snapshot`.
|
||||||
3. Hard deny list for any write/spend path (even if not implemented).
|
3. Hard deny list for any write/spend path (even if not implemented).
|
||||||
4. OpenBao fetch only inside service (AppRole or OIDC role).
|
4. OpenBao fetch only inside service using a dedicated assistant runtime role.
|
||||||
5. Smoke: CI uses mock Qonto; manual: live read against dogfood account.
|
5. Baseline guardrails ship in Phase 1: bounded pagination, timeouts, basic
|
||||||
6. Script replaces ad-hoc first-pull for CostRunRate refresh.
|
rate limiting, bounded concurrency, and redaction tests.
|
||||||
|
6. Smoke: CI uses mock Qonto; manual live read stays explicitly opt-in.
|
||||||
|
7. Script replaces ad-hoc first-pull for CostRunRate refresh.
|
||||||
|
|
||||||
### Phase 2 — MCP surface for all harnesses
|
### Phase 2 — MCP surface for all harnesses
|
||||||
|
|
||||||
1. Streamable-HTTP MCP on the same decision function.
|
1. Streamable-HTTP MCP adapter on the same capability core and policy engine.
|
||||||
2. Document **one** client config snippet for Claude/Codex/Cursor/Grok:
|
2. Document **one** client config snippet for Claude/Codex/Cursor/Grok:
|
||||||
URL + OIDC/workload auth — **no bank secrets**.
|
URL + OIDC/workload auth — **no bank secrets**.
|
||||||
3. agent-harness tool profile `finance-qonto-read` → assistant only.
|
3. agent-harness tool profile `finance-qonto-read` → assistant only.
|
||||||
4. Audit events to State Hub.
|
4. MCP and REST emit the same structured audit schema; State Hub receives
|
||||||
|
operator-level progress notes, not per-call hot-path events.
|
||||||
|
|
||||||
### Phase 3 — Flex-auth + fleet
|
### Phase 3 — Flex-auth + fleet
|
||||||
|
|
||||||
1. flex-auth resource `finance.qonto.read` (+ later `.export`).
|
1. flex-auth resource `finance.qonto.read` (+ later `.export`).
|
||||||
2. Rate limits, concurrency caps, optional response redaction modes.
|
2. Graduated quotas, differentiated response redaction profiles, and finer
|
||||||
|
capability scopes such as `.export` if ever approved.
|
||||||
3. Optional Envoy/gateway in front for multi-assistant mesh.
|
3. Optional Envoy/gateway in front for multi-assistant mesh.
|
||||||
|
|
||||||
### Phase 4 — Productization (dogfood → offer)
|
### Phase 4 — Productization (dogfood → offer)
|
||||||
|
|
@ -439,6 +571,8 @@ Checklist for any new harness:
|
||||||
| Vendor adds new MCP write tools | We do not auto-mirror vendor catalog; allow-list is ours |
|
| 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 |
|
| 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 |
|
| Over-filtering legitimate finance work | Explicit allow tools + CostRunRate helpers; expand by policy PR with tests |
|
||||||
|
| Cross-tenant data mix-up later | Tenant id explicit in claims, policy, audit, and OpenBao path resolution from v1 |
|
||||||
|
| Audit/log sink leaks sensitive fields | Redaction-by-class policy + tests; State Hub not used as raw per-request event store |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -447,8 +581,9 @@ Checklist for any new harness:
|
||||||
1. **Adopt domain-assistant architecture (B)** for Qonto (this blueprint).
|
1. **Adopt domain-assistant architecture (B)** for Qonto (this blueprint).
|
||||||
2. **Repo home:** new `qonto-assistant` vs wait for generic `finance-connect`.
|
2. **Repo home:** new `qonto-assistant` vs wait for generic `finance-connect`.
|
||||||
3. **v1 policy freeze:** no spend / no volume-cost tools — hard deny.
|
3. **v1 policy freeze:** no spend / no volume-cost tools — hard deny.
|
||||||
4. **MCP transport:** remote streamable-HTTP only for production clients.
|
4. **Service identity:** dedicated assistant runtime role; no harness role reuse.
|
||||||
5. **Whether** to ever run vendor MCP as internal backend (default: **no**).
|
5. **MCP transport:** remote streamable-HTTP only for production clients.
|
||||||
|
6. **Whether** to ever run vendor MCP as internal backend (default: **no**).
|
||||||
|
|
||||||
Suggested workplan slug: `BINKY-WP-0006` or a Coulomb-side
|
Suggested workplan slug: `BINKY-WP-0006` or a Coulomb-side
|
||||||
`QONTO-WP-0001` once the repo exists.
|
`QONTO-WP-0001` once the repo exists.
|
||||||
|
|
@ -486,8 +621,10 @@ Suggested workplan slug: `BINKY-WP-0006` or a Coulomb-side
|
||||||
1. **Do not** wire Qonto MCP into each harness.
|
1. **Do not** wire Qonto MCP into each harness.
|
||||||
2. **Do** build a **Qonto Governed Assistant** with:
|
2. **Do** build a **Qonto Governed Assistant** with:
|
||||||
- sole possession of bank credentials,
|
- sole possession of bank credentials,
|
||||||
|
- a protocol-neutral capability core behind REST and MCP adapters,
|
||||||
- default-deny tool catalog,
|
- default-deny tool catalog,
|
||||||
- **no spend / no volume-cost** policy as code,
|
- **no spend / no volume-cost** policy as code,
|
||||||
|
- data classification/redaction rules,
|
||||||
- MCP + REST for all clients,
|
- MCP + REST for all clients,
|
||||||
- flex-auth + OpenBao + audit.
|
- flex-auth + OpenBao + audit.
|
||||||
3. Treat generic MCP gateways as a **later mesh layer**, not a substitute
|
3. Treat generic MCP gateways as a **later mesh layer**, not a substitute
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "Phase 1 — policy kernel and read-only REST"
|
title: "Phase 1 — policy kernel and read-only REST"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: qonto-assistant
|
repo: qonto-assistant
|
||||||
status: ready
|
status: active
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: the-custodian
|
topic_slug: the-custodian
|
||||||
created: "2026-07-21"
|
created: "2026-07-21"
|
||||||
|
|
@ -15,9 +15,12 @@ state_hub_workstream_id: "1540afc2-219e-4d95-96f5-4b45fc6a7aaa"
|
||||||
# Phase 1 — policy kernel and read-only REST
|
# Phase 1 — policy kernel and read-only REST
|
||||||
|
|
||||||
Execute **Phase 1** of `specs/ArchitectureBlueprint.md`: a service skeleton
|
Execute **Phase 1** of `specs/ArchitectureBlueprint.md`: a service skeleton
|
||||||
with a shared `decide(tool, args, claims) → Allow|Deny` policy kernel and a
|
with a protocol-neutral capability core and shared
|
||||||
minimal REST surface for org/accounts/transactions/snapshot. No MCP yet (Phase 2).
|
`decide(request, claims) → Allow|Deny` policy kernel plus a minimal REST
|
||||||
No spend or volume-cost tools — default deny.
|
surface for org/accounts/transactions/snapshot. No MCP yet (Phase 2). No
|
||||||
|
spend or volume-cost tools — default deny. Baseline guardrails ship in this
|
||||||
|
phase: bounded pagination/timeouts, basic rate limiting, bounded concurrency,
|
||||||
|
and redaction tests.
|
||||||
|
|
||||||
**Depends on:** live OpenBao path `tenants/binky/qonto-api` (BINKY-WP-0005 /
|
**Depends on:** live OpenBao path `tenants/binky/qonto-api` (BINKY-WP-0005 /
|
||||||
CCR-2026-0008) — already provisioned.
|
CCR-2026-0008) — already provisioned.
|
||||||
|
|
@ -26,7 +29,7 @@ CCR-2026-0008) — already provisioned.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T01
|
id: QONTO-WP-0002-T01
|
||||||
status: todo
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "f9e129f3-5bd4-43e1-b7a0-281e4d3dec2a"
|
state_hub_task_id: "f9e129f3-5bd4-43e1-b7a0-281e4d3dec2a"
|
||||||
```
|
```
|
||||||
|
|
@ -37,13 +40,14 @@ fleet standard dictates otherwise). Scaffold package layout, `pyproject.toml`
|
||||||
commands in `AGENTS.md` / complete QONTO-WP-0001-T02.
|
commands in `AGENTS.md` / complete QONTO-WP-0001-T02.
|
||||||
|
|
||||||
Done when: `make test` (or documented equivalent) runs an empty/smoke suite;
|
Done when: `make test` (or documented equivalent) runs an empty/smoke suite;
|
||||||
layout matches blueprint components (policy, qonto client, api, audit).
|
layout matches blueprint components (protocol adapters, capability core,
|
||||||
|
policy, qonto client, api, audit).
|
||||||
|
|
||||||
## Task: Policy kernel — default-deny no-spend / no-volume-cost
|
## Task: Policy kernel — default-deny no-spend / no-volume-cost
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T02
|
id: QONTO-WP-0002-T02
|
||||||
status: todo
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "552ff651-dc66-4e65-97fe-3ec26652bbdd"
|
state_hub_task_id: "552ff651-dc66-4e65-97fe-3ec26652bbdd"
|
||||||
```
|
```
|
||||||
|
|
@ -52,8 +56,10 @@ Implement declarative policy (YAML or equivalent) + pure decision function:
|
||||||
|
|
||||||
- default **deny**
|
- default **deny**
|
||||||
- allow only v1 read capability ids (`org_summary`, `list_transactions`,
|
- allow only v1 read capability ids (`org_summary`, `list_transactions`,
|
||||||
`cost_run_rate_hints` / snapshot)
|
`cost_run_rate_hints`, `snapshot_bundle`)
|
||||||
- hard deny classes: `spend`, `volume_cost`, `credential_exfil`
|
- hard deny classes: `spend`, `volume_cost`, `credential_exfil`
|
||||||
|
- stable deny reasons for authz, tenant scope, argument constraint, and
|
||||||
|
credential exfil cases
|
||||||
- unit tests: allow known reads; deny transfer/card/invoice-shaped tools and
|
- unit tests: allow known reads; deny transfer/card/invoice-shaped tools and
|
||||||
suspicious args even if somehow invoked
|
suspicious args even if somehow invoked
|
||||||
|
|
||||||
|
|
@ -63,7 +69,7 @@ Done when: policy tests pass in CI/local; no network required.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T03
|
id: QONTO-WP-0002-T03
|
||||||
status: todo
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "be3aa7b6-f28c-4436-bd5d-d6940de6c2ce"
|
state_hub_task_id: "be3aa7b6-f28c-4436-bd5d-d6940de6c2ce"
|
||||||
```
|
```
|
||||||
|
|
@ -73,10 +79,12 @@ Implement thirdparty client using `Authorization: login:key` (fields
|
||||||
|
|
||||||
- organization + bank accounts
|
- organization + bank accounts
|
||||||
- paginated transactions with hard caps
|
- paginated transactions with hard caps
|
||||||
|
- bounded timeouts and conservative retry behavior
|
||||||
- never log Authorization or key material
|
- never log Authorization or key material
|
||||||
|
|
||||||
Support env inject for tests (`QONTO_API_KEY`/`QONTO_ORGANIZATION_ID` or
|
Support env inject for tests (`QONTO_API_KEY`/`QONTO_ORGANIZATION_ID` or
|
||||||
`API_KEY`/`API_USER`) and document OpenBao fetch for operators.
|
`API_KEY`/`API_USER`) and document OpenBao fetch for operators. Production
|
||||||
|
path fetches through a dedicated assistant runtime role only.
|
||||||
|
|
||||||
Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
||||||
|
|
||||||
|
|
@ -84,7 +92,7 @@ Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T04
|
id: QONTO-WP-0002-T04
|
||||||
status: todo
|
status: progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "678b0b26-15af-4037-849f-d24d320588ac"
|
state_hub_task_id: "678b0b26-15af-4037-849f-d24d320588ac"
|
||||||
```
|
```
|
||||||
|
|
@ -96,23 +104,26 @@ Expose JSON endpoints that all run through the policy kernel:
|
||||||
| GET | `/v1/health` | no bank call |
|
| GET | `/v1/health` | no bank call |
|
||||||
| GET | `/v1/accounts` | org_summary |
|
| GET | `/v1/accounts` | org_summary |
|
||||||
| GET | `/v1/transactions` | list_transactions (capped) |
|
| GET | `/v1/transactions` | list_transactions (capped) |
|
||||||
| GET | `/v1/snapshot` | cost/run-rate oriented summary |
|
| GET | `/v1/snapshot` | snapshot_bundle (composed allowed reads) |
|
||||||
|
|
||||||
Done when: OpenAPI or documented curl examples; integration test with mock
|
Done when: OpenAPI or documented curl examples; integration test with mock
|
||||||
client; deny paths return 403 with stable error code.
|
client; deny paths return 403 with stable error code; route-to-capability
|
||||||
|
mapping is explicit and shared with future MCP.
|
||||||
|
|
||||||
## Task: Audit metadata (no secrets)
|
## Task: Audit metadata (no secrets)
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T05
|
id: QONTO-WP-0002-T05
|
||||||
status: todo
|
status: progress
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "4a42dff1-1281-4cc1-ba6a-24702bce7dc9"
|
state_hub_task_id: "4a42dff1-1281-4cc1-ba6a-24702bce7dc9"
|
||||||
```
|
```
|
||||||
|
|
||||||
Log or emit structured audit events: actor (if present), capability, decision,
|
Log or emit structured audit events: actor (if present), capability, decision,
|
||||||
deny_reason, latency, upstream HTTP status. Never secret fields. Prefer
|
deny_reason, latency, upstream HTTP status, and policy version. Never secret
|
||||||
stdout JSON + optional State Hub progress for dogfood runs.
|
fields. Prefer structured stdout JSON (or equivalent sink) on the hot path;
|
||||||
|
State Hub progress stays optional roll-up evidence for dogfood runs, not
|
||||||
|
per-call audit storage.
|
||||||
|
|
||||||
Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
||||||
|
|
||||||
|
|
@ -120,7 +131,7 @@ Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: QONTO-WP-0002-T06
|
id: QONTO-WP-0002-T06
|
||||||
status: todo
|
status: progress
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "7b7ca0f7-f523-473e-b3f6-fe0564f54ed5"
|
state_hub_task_id: "7b7ca0f7-f523-473e-b3f6-fe0564f54ed5"
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue