From 67a5b01e08640fc84a36060194a426c708753e74 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 22 Jul 2026 00:31:50 +0200 Subject: [PATCH] Seeded intent and initial workplan --- INTENT.md | 2 +- specs/ArchitectureBlueprint.md | 331 +++++++++++++----- .../QONTO-WP-0002-policy-kernel-and-rest.md | 45 ++- 3 files changed, 263 insertions(+), 115 deletions(-) diff --git a/INTENT.md b/INTENT.md index 9cc3aab..4692762 100644 --- a/INTENT.md +++ b/INTENT.md @@ -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. diff --git a/specs/ArchitectureBlueprint.md b/specs/ArchitectureBlueprint.md index df0236a..45396f3 100644 --- a/specs/ArchitectureBlueprint.md +++ b/specs/ArchitectureBlueprint.md @@ -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//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//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 diff --git a/workplans/QONTO-WP-0002-policy-kernel-and-rest.md b/workplans/QONTO-WP-0002-policy-kernel-and-rest.md index 6ce2f02..61b60e5 100644 --- a/workplans/QONTO-WP-0002-policy-kernel-and-rest.md +++ b/workplans/QONTO-WP-0002-policy-kernel-and-rest.md @@ -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" ```