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*.
|
||||
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
|
||||
5. **Platform fit** — flex-auth, OpenBao, ops-warden, State Hub evidence; no
|
||||
parallel IAM.
|
||||
6. **Autonomy lanes** — map to binky-control AutonomyPolicy: reads Green/Blue;
|
||||
plan change / transfers / API key minting remain Red.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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).
|
||||
> Context: BINKY-WP-0005 finished (live OpenBao lane + first read-only pull).
|
||||
> 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
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Agent clients (many) │
|
||||
│ Claude Code · Codex · Cursor · Grok · agent-harness sessions · scripts │
|
||||
│ Agent clients │
|
||||
│ 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
|
||||
│ Qonto Governed Assistant («qonto-assistant») │
|
||||
│ │
|
||||
│ Authn/Authz edge │
|
||||
│ OIDC / workload id / mTLS + optional flex-auth │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Protocol adapters │
|
||||
│ MCP adapter REST adapter │
|
||||
│ │ │ │
|
||||
│ └──────────┬────────┘ │
|
||||
│ ▼ │
|
||||
│ Capability service core │
|
||||
│ canonical capability ids + response contracts │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Policy engine │
|
||||
│ default-deny, tenant scope, arg constraints, data class rules │
|
||||
│ │ │
|
||||
│ ┌─────┴──────────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ 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**
|
||||
|
|
@ -219,65 +230,102 @@ shelling out to unmaintained MCP.
|
|||
| **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)
|
||||
### 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
|
||||
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 routes and MCP tools are only transport adapters. They must translate
|
||||
> into the same internal `CapabilityRequest` model before policy runs.
|
||||
|
||||
**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.
|
||||
- Same policy middleware as MCP (shared library, one decision function).
|
||||
Canonical request shape:
|
||||
|
||||
Example tool catalog (v1 — illustrative):
|
||||
```text
|
||||
CapabilityRequest
|
||||
capability_id
|
||||
tenant_id
|
||||
actor_claims
|
||||
resource_scope
|
||||
request_args
|
||||
protocol # rest | mcp
|
||||
```
|
||||
|
||||
| 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 |
|
||||
Recommended v1 capability map:
|
||||
|
||||
| Internal capability id | REST surface | MCP surface | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `org_summary` | `GET /v1/accounts` | `qonto_org_summary` | Core balances/org read |
|
||||
| `list_transactions` | `GET /v1/transactions` | `qonto_list_transactions` | Capped, filtered history |
|
||||
| `cost_run_rate_hints` | used by `GET /v1/snapshot` | `qonto_cost_run_rate_hints` (Phase 2) | Normalized finance hints, not raw export |
|
||||
| `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)
|
||||
|
||||
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
|
||||
# policy/qonto-v1.yaml (sketch)
|
||||
version: 1
|
||||
default: deny
|
||||
allow:
|
||||
- id: org_summary
|
||||
- id: list_transactions
|
||||
capabilities:
|
||||
org_summary:
|
||||
lanes: [green, blue]
|
||||
list_transactions:
|
||||
lanes: [green, blue]
|
||||
constraints:
|
||||
max_per_page: 100
|
||||
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:
|
||||
- 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
|
||||
spend:
|
||||
match_prefixes: [create_, issue_, transfer_, payout_]
|
||||
volume_cost:
|
||||
match_tags: [card_operation, invoice_send, payment_link, subscription_change]
|
||||
credential_exfil:
|
||||
response_fields: [api_key, authorization_header, full_iban]
|
||||
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
|
||||
green_blue:
|
||||
allow_set: [org_summary, list_transactions, cost_run_rate_hints, snapshot_bundle]
|
||||
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
|
||||
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).
|
||||
- Reject unknown capability ids before touching Qonto.
|
||||
- Enforce tenant binding before resource lookup; callers do not choose an
|
||||
arbitrary OpenBao path or tenant id.
|
||||
- Inspect arguments for suspicious write semantics even if a route/tool is
|
||||
mis-wired later.
|
||||
- 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:
|
||||
|
||||
|
|
@ -289,21 +337,117 @@ Map to AutonomyPolicy:
|
|||
|
||||
### 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
|
||||
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)
|
||||
Caller
|
||||
-> authenticates to qonto-assistant (OIDC workload identity, mTLS, or similar)
|
||||
-> assistant checks claims + optional flex-auth grant (finance.qonto.read)
|
||||
-> assistant resolves tenant from claims/policy
|
||||
-> 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.
|
||||
- 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.
|
||||
Rules:
|
||||
|
||||
### 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 |
|
||||
| --- | --- | --- |
|
||||
|
|
@ -316,24 +460,6 @@ 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
|
||||
|
|
@ -376,25 +502,31 @@ llm-connect; route **bank actions** through qonto-assistant.
|
|||
|
||||
### 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`.
|
||||
1. Service skeleton with a protocol-neutral capability core and shared
|
||||
`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).
|
||||
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.
|
||||
4. OpenBao fetch only inside service using a dedicated assistant runtime role.
|
||||
5. Baseline guardrails ship in Phase 1: bounded pagination, timeouts, basic
|
||||
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
|
||||
|
||||
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:
|
||||
URL + OIDC/workload auth — **no bank secrets**.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### 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 |
|
||||
| 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 |
|
||||
| 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).
|
||||
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**).
|
||||
4. **Service identity:** dedicated assistant runtime role; no harness role reuse.
|
||||
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
|
||||
`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.
|
||||
2. **Do** build a **Qonto Governed Assistant** with:
|
||||
- sole possession of bank credentials,
|
||||
- a protocol-neutral capability core behind REST and MCP adapters,
|
||||
- default-deny tool catalog,
|
||||
- **no spend / no volume-cost** policy as code,
|
||||
- data classification/redaction rules,
|
||||
- MCP + REST for all clients,
|
||||
- flex-auth + OpenBao + audit.
|
||||
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"
|
||||
domain: infotech
|
||||
repo: qonto-assistant
|
||||
status: ready
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: the-custodian
|
||||
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
|
||||
|
||||
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.
|
||||
with a protocol-neutral capability core and shared
|
||||
`decide(request, claims) → Allow|Deny` policy kernel plus a minimal REST
|
||||
surface for org/accounts/transactions/snapshot. No MCP yet (Phase 2). No
|
||||
spend or volume-cost tools — default deny. Baseline guardrails ship in this
|
||||
phase: bounded pagination/timeouts, basic rate limiting, bounded concurrency,
|
||||
and redaction tests.
|
||||
|
||||
**Depends on:** live OpenBao path `tenants/binky/qonto-api` (BINKY-WP-0005 /
|
||||
CCR-2026-0008) — already provisioned.
|
||||
|
|
@ -26,7 +29,7 @@ CCR-2026-0008) — already provisioned.
|
|||
|
||||
```task
|
||||
id: QONTO-WP-0002-T01
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
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.
|
||||
|
||||
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
|
||||
id: QONTO-WP-0002-T02
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "552ff651-dc66-4e65-97fe-3ec26652bbdd"
|
||||
```
|
||||
|
|
@ -52,8 +56,10 @@ 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)
|
||||
`cost_run_rate_hints`, `snapshot_bundle`)
|
||||
- hard deny classes: `spend`, `volume_cost`, `credential_exfil`
|
||||
- stable deny reasons for authz, tenant scope, argument constraint, and
|
||||
credential exfil cases
|
||||
- unit tests: allow known reads; deny transfer/card/invoice-shaped tools and
|
||||
suspicious args even if somehow invoked
|
||||
|
||||
|
|
@ -63,7 +69,7 @@ Done when: policy tests pass in CI/local; no network required.
|
|||
|
||||
```task
|
||||
id: QONTO-WP-0002-T03
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "be3aa7b6-f28c-4436-bd5d-d6940de6c2ce"
|
||||
```
|
||||
|
|
@ -73,10 +79,12 @@ Implement thirdparty client using `Authorization: login:key` (fields
|
|||
|
||||
- organization + bank accounts
|
||||
- paginated transactions with hard caps
|
||||
- bounded timeouts and conservative retry behavior
|
||||
- never log Authorization or key material
|
||||
|
||||
Support env inject for tests (`QONTO_API_KEY`/`QONTO_ORGANIZATION_ID` or
|
||||
`API_KEY`/`API_USER`) and document OpenBao fetch for operators.
|
||||
`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.
|
||||
|
||||
|
|
@ -84,7 +92,7 @@ Done when: unit tests with mocked HTTP; optional live smoke behind a flag.
|
|||
|
||||
```task
|
||||
id: QONTO-WP-0002-T04
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
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/accounts` | org_summary |
|
||||
| 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
|
||||
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
|
||||
id: QONTO-WP-0002-T05
|
||||
status: todo
|
||||
status: progress
|
||||
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.
|
||||
deny_reason, latency, upstream HTTP status, and policy version. Never secret
|
||||
fields. Prefer structured stdout JSON (or equivalent sink) on the hot path;
|
||||
State Hub progress stays optional roll-up evidence for dogfood runs, not
|
||||
per-call audit storage.
|
||||
|
||||
Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
||||
|
||||
|
|
@ -120,7 +131,7 @@ Done when: tests assert secrets absent from log lines for a sample allow/deny.
|
|||
|
||||
```task
|
||||
id: QONTO-WP-0002-T06
|
||||
status: todo
|
||||
status: progress
|
||||
priority: medium
|
||||
state_hub_task_id: "7b7ca0f7-f523-473e-b3f6-fe0564f54ed5"
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue