The generated index had drifted from the workplan files. The opt-in lets
the pre-commit hook rebuild WORK-RECORDS.md in the same commit as the
workplan it indexes.
Assistant: grok
Assistant-Session: 01a17f9d-fe9d-7d72-9a22-6304f1a2a790
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 8340@bnt-lap001
Assistant-Session: 2e192b3d-b3e8-4403-94d4-d55a2c8b476c
Updated by fix-consistency on 2026-09-24:
- update .custodian-brief.md for qonto-assistant
Assistant: grok
Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 7184@bnt-lap001
Assistant-Session: 4b436ae2-b17c-4b40-8754-200b161b26e4
statehub register left the literal token (fixed in state-hub 19450d7).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 7184@bnt-lap001
Assistant-Session: 4b436ae2-b17c-4b40-8754-200b161b26e4
`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
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
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>
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>
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>
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>
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>
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>
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>