Commit graph

54 commits

Author SHA1 Message Date
3347aa5469 fix(workplans): qualify ad-hoc identifiers with the repository prefix
`ADHOC-YYYY-MM-DD` is unique per date but not per repository, so any two repos
opening an ad-hoc on the same day collide. The 2026-08-26 fleet projection
reset refused 9 records for exactly this reason.

Canon (work-record-types_v0.1, CUST-WP-0066) settled the form as
`{PREFIX}-WP-ADHOC-YYYY-MM-DD`, filename unchanged, and grandfathered existing
ids on the condition they are never *silently* re-derived. This is the explicit
migration that clause allows for.

The hub id is derived from the record id, so a changed id is a different
record: stale state_hub_*_id fields are dropped and fix-consistency re-derives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-28 00:28:23 +02:00
7b23a5798f docs(agents): repoint remote State Hub URL to the in-cluster address
The remote row pointed at 127.0.0.1:18000, a reverse tunnel back to the
workstation. On railiance01 the State Hub runs in the cluster on that same
machine, so the request left the box and came back to reach a local service.

Refs CUST-WP-0067-T07

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-25 00:21:41 +02:00
efdf00352a docs: point at RMASTER-WP-0019 after master prefix rename 2026-08-14 14:29:19 +02:00
5255077ac4 Adopt Target Revenue Source License V1C1 (org-wide preliminary rollout)
Maintainer decision, 2026-07-29: adopts TRSL V1C1 as this repo's
preliminary governing license, per target-revenue's
workplans/TREV-WP-0008-governance-and-pilot-rollout.md T05. Full
specialist legal review is deferred until out of beta (target-revenue
SCOPE.md §1). No Phase is yet declared for this repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 00:46:41 +02:00
1969961921 Package qonto runtime container 2026-07-27 21:10:49 +02:00
57617089ce Refresh Qonto work record index 2026-07-27 19:19:36 +02:00
custodian-sync
838e184cf2 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-27:
  - update .custodian-brief.md for qonto-assistant
2026-07-27 19:18:52 +02:00
7595872b1b Finish Qonto security hardening plan 2026-07-27 19:18:21 +02:00
a8a3f7dc25 Define Qonto Knative runtime handoff 2026-07-26 13:34:56 +02:00
custodian-sync
632966eaea chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-26:
  - update .custodian-brief.md for qonto-assistant
2026-07-26 13:09:30 +02:00
custodian-sync
610e0fa939 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-26:
  - update .custodian-brief.md for qonto-assistant
2026-07-26 11:03:31 +02:00
de45158086 chore(consistency): sync T06 status from DB [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:28:56 +02:00
custodian-sync
6c7dcc6e83 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-24:
  - update .custodian-brief.md for qonto-assistant
2026-07-24 00:28:28 +02:00
00191ee841 QONTO-WP-0004: mark T06 done
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:28:09 +02:00
9fbcef7b05 QONTO-WP-0004-T06: draft isolation placement manifests + railiance/app.toml
Concrete, reviewable placement request per docs/SecurityPractice.md #7
(I1 Reinforced minimum: dedicated node pool/namespace, given internet
reachability via the facade and sole custody of the bank credential).

deploy/k8s/qonto-assistant/: modeled on llm-connect's real deployment
(deploy/k8s/activity-core-llm-connect/) but tightened -- dedicated
namespace (not shared), Deployment starts at replicas:0 (meant to be
scaled 0<->1 by the facade, QONTO-WP-0004-T05), ClusterIP-only Service,
NetworkPolicy default-deny with ingress limited to the facade and
egress limited to DNS+443 (the FQDN-egress limitation is called out
explicitly, not silently widened). Verified: `kubectl kustomize`
renders all six resources cleanly.

externalsecret.yaml depends on CCR-2026-0009 (new), proposed in
railiance-platform: a workload-scoped Kubernetes-auth access lane into
the existing tenants/binky/qonto-api credential, since CCR-2026-0008
is human/OIDC admin access only and unusable by a running pod. Mirrors
CCR-2026-0003's llm-connect/ExternalSecretsOperator pattern. Paired
draft ClusterSecretStore also added there. Both validated against
schemas/credential-change-request.schema.yaml. Status: proposed, not
approved -- explicitly not something this repo can complete alone.

railiance/app.toml: staged-promotion contract (criticality=critical,
mandatory human approval before Stage 2 traffic exposure and Stage 3
promotion, per railiance-cluster/docs/app-toml-contract.md's own rule
for production-critical workloads). Validated against
railiance-cluster/schemas/railiance-app.schema.json.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:27:37 +02:00
60d4b9f8da chore(consistency): sync T03/T04 status from DB [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:21:32 +02:00
custodian-sync
1e449f6083 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-24:
  - update .custodian-brief.md for qonto-assistant
2026-07-24 00:21:24 +02:00
691ec85ad5 QONTO-WP-0004: mark T03/T04 done
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:21:04 +02:00
1f0f979e36 QONTO-WP-0004-T04: live flex-auth + tenant-engine authorization gate
Replaces the config-only QONTO_ASSISTANT_ENFORCE_SCOPE cached-claim
check with two live-checked facts, per docs/SecurityPractice.md #4:

1. flex-auth POST /v1/check on finance.qonto.read for the calling
   actor/tenant (FlexAuthCheckClient, modeled on tenant-engine's own
   client for the same API). Registration lives in the flex-auth repo
   (examples/qonto-assistant/) -- rules + embedded tests verified with
   flex-auth test-policy/load-registry/check, and a live flex-auth
   serve hit by this exact client over real HTTP (not a mock).
2. tenant-engine's live capability-role lookup
   (GET /tenants/{id}/roles/live), denying unless the tenant currently
   holds one of QONTO_TENANT_ENGINE_REQUIRED_ROLES (default VEN,CUS) --
   optional and additive to the flex-auth check.

Both clients fail closed by construction (unreachable/malformed/non-2xx
all deny, never grant), matching FlexAuthCheckClient's existing
fail-closed philosophy elsewhere in the fleet. LiveAuthorizationGate
combines both and is wired into CapabilityService._execute ahead of
the internal policy kernel; off by default (no QONTO_FLEX_AUTH_URL
set) so existing deployments are unaffected until configured.

Verified beyond mocked unit tests: ran a real `flex-auth serve` loaded
with the registered policy, and a real tenant-engine instance seeded
with a VEN grant for tenant:friendly:binky, and exercised this repo's
actual FlexAuthCheckClient/TenantEngineClient/LiveAuthorizationGate
against both live processes over real HTTP -- allow for the correct
tenant, live_authz_denied for a mismatched tenant.

28 new unit tests (flex_auth_client, tenant_engine_client,
live_authorization_gate + CapabilityService integration). Full suite
-> 80 passed; REST/MCP smokes and compileall still clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:19:04 +02:00
aa28ef353a QONTO-WP-0004-T03: verify key-cape IAM Profile tokens
Replaces the interim shared-secret bearer token's role as the identity
boundary with real key-cape JWKS-based verification, closing the gap
docs/mcp-integration.md called out explicitly ("no OIDC issuer exists
in this fleet yet") -- key-cape's /jwks is a standard RS256 endpoint
and needed no key-cape-side work to consume.

KeyCapeTokenVerifier fetches and caches signing keys over httpx
(matching FlexAuthCheckClient's pattern elsewhere in this codebase),
validates iss/aud/exp and the IAM Profile v0.3 required claims, and
derives ActorClaims from the token (tenant, scopes, and a lane
inferred from the roles claim). Wired into auth.actor_claims_from_headers,
the single seam both REST and MCP already used -- a verified bearer
token now takes precedence over self-asserted X-Actor-* headers, and
can be made mandatory via QONTO_KEY_CAPE_REQUIRED once real tokens are
issued to callers. Off by default (no QONTO_KEY_CAPE_JWKS_URL set) so
existing deployments are unaffected until configured.

The QONTO_ASSISTANT_MCP_TOKEN shared secret remains as a documented
local-dev/legacy fallback, not the auth boundary going forward.

Verified: 13 new tests (test_key_cape_auth.py) covering valid/expired/
wrong-audience/wrong-issuer/missing-claim/unknown-key/rotated-key
tokens plus the auth.py precedence and required-vs-optional
integration paths, using a real generated RSA keypair and JWKS served
over httpx.MockTransport. Full suite -> 52 passed; REST and MCP smokes
both still pass against fixtures; compileall clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:10:41 +02:00
21c98fc062 chore(consistency): write back QONTO-WP-0004 hub IDs
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 23:00:18 +02:00
custodian-sync
dda9fdaf80 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 23:00:09 +02:00
f1a696766b QONTO-WP-0004: security hardening and scale-to-zero facade workplan
Tracks implementation of docs/SecurityPractice.md. T01 (Security
Genome record) and T02 (deny-escalation lockout) are already done,
shipped in the prior commit -- both were dependency-free. T03-T06
depend on key-cape, flex-auth, tenant-engine, and a Railiance
placement decision respectively, and are flagged as such rather than
assumed completable from this repo alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:59:51 +02:00
3faf1fed71 Add SecurityPractice.md, Security Genome record, and deny-escalation lockout
Design doc for hardening qonto-assistant before deployment to
railiance01: this is the first fleet service that must be
internet-reachable (external harness clients, not just in-cluster
jobs) while holding a real bank credential. Covers identity (key-cape
in place of the interim bearer token), authorization (finance.qonto.read
in flex-auth + tenant-engine capability roles instead of the
hardcoded default_tenant_id), network exposure (facade-only internet
address), isolation profile, and a Kings Guard mapping (the existing
audit stream is already Immune-Observation-shaped; nothing to rebuild
later).

Ships one concrete, dependency-free piece of that design now:
DenyEscalationTracker locks out an actor who repeatedly triggers
arg_constraint/credential_exfil denies within a short window, closing
the gap where a probing client could retry indefinitely at whatever
rate the existing rate limiter otherwise allows. Wired through
CapabilityService, on by default, configurable via
QONTO_DENY_ESCALATION_* env vars. Ordinary denies (authz_denied,
tenant_scope) never count toward it.

Also adds specs/security-genome.yaml (kings-guard's genome-record
shape, populated now so no rework is needed once a consumer exists).

Verified: pytest -> 39 passed (8 new); REST and MCP smoke scripts both
pass against fixtures; compileall clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 22:59:06 +02:00
78eb2a819c chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003 closure [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 13:56:38 +02:00
custodian-sync
677c29acf6 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 13:56:27 +02:00
ad500121f4 QONTO-WP-0003-T07: close out Phase 2 MCP workplan
T01-T06 all done. Final end-to-end confirmation before closing: pytest ->
31 passed; both scripts/smoke_rest_api.py and scripts/smoke_mcp.py exit 0
against fixtures in the same run; compileall clean.

REST/MCP policy-outcome parity is proven by test, not just asserted:
test_policy_decision_identical_across_protocols (decide() never branches on
transport) plus test_audit_parity.py (identical audit shape for the same
capability call over both transports, allow and deny paths).

Workplan status -> finished. Closure note records what shipped, the three
known gaps carried forward (OIDC federation, header-based actor identity,
unregistered agent-harness profile), and the Phase 3 seed. README updated
to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 13:56:06 +02:00
09300bd779 chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T06 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:05:02 +02:00
custodian-sync
d9ba449602 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 11:04:56 +02:00
0dc001bf53 QONTO-WP-0003-T06: MCP smoke script + operator runbook update
Add scripts/smoke_mcp.py, same shape as smoke_rest_api.py (random port,
subprocess-launch against QONTO_FIXTURE_DIR, wait on /v1/health, assert,
clean teardown), but goes further: generates a fresh
QONTO_ASSISTANT_MCP_TOKEN per run and connects through it with the mcp
SDK's streamablehttp_client, so the smoke exercises T03's bearer-token gate
instead of bypassing it. Lists tools, calls all four, and confirms an
out-of-catalog tool name comes back as a normal isError result through the
real wire protocol rather than a crash.

Extend docs/operator-runbook.md with a "One-command MCP smoke" section next
to the REST one; cross-link from docs/mcp-integration.md's manual smoke
walkthrough so the two don't drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:04:39 +02:00
c1e33decd9 chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T05 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:01:45 +02:00
custodian-sync
5cd348ffbb chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 11:01:33 +02:00
c3e69373ca QONTO-WP-0003-T05: pin REST/MCP audit-schema parity with a test
CapabilityService._emit_audit was already the single audit call site for
both transports since T02, but nothing failed if a future change diverged
one transport's shape. Add tests/test_audit_parity.py: same capability
called through protocol="rest" and protocol="mcp" (allow path and deny
path) must produce identical audit events except request_id/timestamp/
latency_ms (expected to vary) and protocol (expected to differ). Also pins
down that no audit event ever contains a secret-shaped field name.

Confirmed via grep: no State Hub coupling anywhere in src/qonto_assistant/
-- the only audit sink is AuditLogger, so there's no per-call hot-path
write to accidentally wire up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:01:16 +02:00
0b268faf05 chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T04 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:49:05 +02:00
custodian-sync
5fa5c02598 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 09:48:55 +02:00
1e476309a3 QONTO-WP-0003-T04: document finance-qonto-read tool profile contract
qonto-assistant's side only, per task scope: the contract a profile named
finance-qonto-read must satisfy in agent-harness (~/agent-harness, ADR-001,
ToolProfile) to reach this MCP surface -- lane (green/blue, never red), the
exact mcp__qonto-assistant__* allowed-tools list, and the optional
finance.qonto.read scope.

Checked agent-harness/agent_harness/profiles.py directly: only CLI-only
profiles exist today (green-commit-only, blue-mail-triage), none grant an
MCP server yet. Included a ready-to-paste ToolProfile entry matching its
actual schema, marked as a proposal for agent-harness's own PR -- not
applied here, since registering it is a separate repo/workplan. Added the
end-to-end example: instance manifest -> harness-injected MCP server config
-> the four allowed tool calls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:48:36 +02:00
b96b8cd5b3 chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T03 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:41:49 +02:00
custodian-sync
eb5ca29ba7 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-23:
  - update .custodian-brief.md for qonto-assistant
2026-07-23 09:41:43 +02:00
d2ffd372b5 QONTO-WP-0003-T03: MCP client auth + shared multi-harness config snippet
Gate /mcp with a shared-secret bearer token (QONTO_ASSISTANT_MCP_TOKEN,
mcp_auth.py::BearerTokenAuthMiddleware, constant-time compare, REST
untouched) since no OIDC issuer exists in this fleet yet -- pointing
FastMCP's OAuth Protected Resource flow at a non-existent issuer would be
worse than not having it. This token is a service credential, never a bank
credential; per-actor identity stays the existing X-Actor-* convention.

Add docs/mcp-integration.md: tool catalog, the two-layer auth model (workload
auth today vs. deferred OIDC target), and one shared {"mcpServers": {...}}
client config snippet (url + headers) usable across Claude Code, Claude
Desktop, Cursor, and Codex/Grok-style harnesses.

Verified live using only that snippet: unauthenticated and wrong-token
requests get 401 before reaching any tool; a request built from the
snippet's URL + headers lists tools and calls qonto_org_summary
successfully against the fixture-backed server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:41:25 +02:00
e1e47ae304 chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T02 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:48:40 +02:00
custodian-sync
35b68290aa chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-22:
  - update .custodian-brief.md for qonto-assistant
2026-07-22 21:48:26 +02:00
b4b1dc1c7b QONTO-WP-0003-T02: MCP tool catalog on the shared capability core
Add qonto_org_summary, qonto_list_transactions, and qonto_cost_run_rate_hints
MCP tools, all routed through CapabilityService with protocol="mcp" -- same
PolicyEngine.decide() path as REST, same deny-reason vocabulary. Skip
snapshot_bundle as an MCP tool (REST already covers the composite read; not
a separate privilege).

CapabilityService now threads protocol through _execute/_emit_audit instead
of hardcoding "rest". cost_run_rate_hints gets its own service method since
it's an independent policy capability, not only a snapshot sub-field.

Actor identity reuses REST's X-Actor-* header convention via a shared
auth.actor_claims_from_headers(), read from the MCP Context's request when
present. Fixed streamable_http_path defaulting to "/mcp", which doubled to
"/mcp/mcp" once mounted under the "/mcp" prefix.

Verified end-to-end with the mcp SDK's streamablehttp_client against the
live fixture-backed server: tool list, allow/deny paths, and X-Actor-ID
flowing through to the audit log exactly like REST.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:48:09 +02:00
ba2612682f chore(consistency): sync WORK-RECORDS.md for QONTO-WP-0003-T01 [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:41:54 +02:00
custodian-sync
3827cb722c chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-22:
  - update .custodian-brief.md for qonto-assistant
2026-07-22 21:41:42 +02:00
d4656be310 QONTO-WP-0003-T01: MCP adapter skeleton on shared capability core
Add a streamable-HTTP MCP adapter (mcp_server.py, official FastMCP SDK)
mounted at /mcp in the existing FastAPI app, with a combined lifespan so
the MCP session manager starts/stops with the service. Ships one smoke
tool (qonto_ping, no bank call) — real capability tools land in T02.

Prove REST and MCP hit the identical policy path: PolicyEngine.decide()
never branches on request.protocol, verified by a parametrized test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:41:24 +02:00
dc3431cda0 chore(consistency): register QONTO-WP-0003 in State Hub [auto]
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:37:10 +02:00
custodian-sync
49e3b657a1 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-22:
  - update .custodian-brief.md for qonto-assistant
2026-07-22 21:37:00 +02:00
d643e552f4 Draft QONTO-WP-0003: Phase 2 MCP surface workplan
Streamable-HTTP MCP adapter on the same capability core and policy kernel
as Phase 1's REST surface, per specs/ArchitectureBlueprint.md Phase 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:36:38 +02:00
ca12843013 Complete Phase 1: policy kernel, REST service, and local smoke tooling
Implements QONTO-WP-0002 (policy-gated Qonto REST service with audit
logging, rate limiting, and credential handling) and the ADHOC-2026-07-21
follow-up (fixture-backed local smoke mode, repo classification metadata).
Marks QONTO-WP-0001/0002 and the ad-hoc workplan finished, and regenerates
WORK-RECORDS.md and the ADHOC workplan's state_hub_workstream_id via
fix-consistency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:21:05 +02:00
custodian-sync
eef408bb19 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-22:
  - update .custodian-brief.md for qonto-assistant
2026-07-22 01:08:02 +02:00