Commit graph

6 commits

Author SHA1 Message Date
51e541c9a1 Adopt the agent environment orientation for KeyCape's live objects.
Write Secrets with kubectl replace in the rotation script, since apply copies
the data into the last-applied annotation. Record in operations.md the
live-change rules that apply here and the open finding: four live Secrets
carry that annotation, and removing it waits for the founder's go-ahead.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 352750@bnt-lap001
Assistant-Session: de41ef1c-2113-4dd2-9b92-f318ffa7f98b
2026-09-23 22:07:23 +02:00
074c2ce498 Add central login recovery and confirmed shared sign-out
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 44s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 10:34:41 +02:00
7a73352368 Record the custody owner's confirmations and guard a receipt against misreading
railiance-platform replied on all three open threads. Recording what they add
rather than what we already knew.

T02: they reached the same reading of the verifier receipt independently and add
human_client_consume_denied: true for both clients, confirm the verifier ran
twice (activation and post-rollout) at generation 38 with the signing key
unchanged, and state T02 can close against that receipt. They also correct our
framing, rightly: the packet says client-side retrieval is unadmitted, which
stays true, but the attended operator path is not a client-side read and never
required one. This task had been treating those as the same constraint.

T04: the live approval clients mean this repo now holds a committed receipt that
looks like rotation evidence and is not. Both owners independently state the same
two gaps -- no real predecessor rotation, no observed wall-clock expiry. Recorded
in T04 rather than only in T02, because verify-client's predecessor rejection is
now implemented and unproven, which is a different state from missing or done,
and T04 is where that distinction belongs.

Answered their open question on CCR-2026-0020, which has no named presenting
actor. As issuer: the registration carries both approval:create and
approval:approve, so one presenter can create an entry and approve it. That is a
separation-of-duties property of the holder, not a defect in the token -- KeyCape
issues the grants approval-engine asked for. Recorded both shapes KeyCape can
support, and that who holds it is approval-engine's decision and the doctrine
question gate-house's, not ours.

Also narrowed the operations note: the deployed config has not been written since
the activation, so the inspected state is the state that will boot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uV8zoCKpA1WRAxsKRYbdH

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1182213@bnt-lap001
Assistant-Session: 966597b9-ae61-46a4-8b9e-1594ab3ec4ad
2026-09-09 20:05:40 +02:00
5ae80d341f Record the two unreleased fail-closed startup changes for the rollout
Both changes on main that postdate the deployed image affect startup or issuance
and both fail closed, so a mistake in either presents as a refusal rather than a
warning. That fact currently lives only in a State Hub message to the deployment
owner; messages age out of attention, and the repo is what the next session
reads, so operations.md is where it belongs.

Records what was actually checked rather than what is assumed: the deployed image
digest and the commit it was built from, that the live config's three browser
clients carry neither newly-rejected field, and that no deployed client declares
a tenant on a browser grant so the refusal is not yet reachable. Also states
plainly that neither change has been exercised against a running issuer, and that
the proof belongs to the attended window rather than to a session running against
production on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uV8zoCKpA1WRAxsKRYbdH

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1182213@bnt-lap001
Assistant-Session: 966597b9-ae61-46a4-8b9e-1594ab3ec4ad
2026-09-09 16:41:38 +02:00
7dda967c27 Establish the live state and find a rollout precondition for G10
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 45s
G10 waits on custody and platform owners and cannot close from here. What was
doable: verify the handoffs actually went out, replace a remembered live state
with an observed one, and find out whether main is safe to deploy. The last
question found a defect in this repository's own recent work.

Handoffs verified independently rather than trusted: all seven messages are in
the hub with receipt ids. This gap was reopened once for claimed-but-unsent
delivery, so the claim deserved the same scrutiny.

Live state read from the cluster read-only: image main-153258b, only the Qonto
secret materialized so the approval clients remain unprovisioned, four registered
clients, no tenantEngine block. That also corrects an earlier claim of mine --
the deployed config sets userOU explicitly, so the KEY-WP-0023 default fix was
never a production issue.

The precondition: KEY-WP-0019 discovers the expected issuer from
authelia.tokenBaseURL, and the deployed Authelia derives its advertised issuer
from the request Host, advertising the in-cluster address to KeyCape and the
browser-facing one to browsers. Verification fails closed, so a mismatch breaks
every human login and looks like a broken login rather than a misconfiguration.
Which value the token carries needs a real login against production to settle and
was not determined here.

Two mitigations: docs/operations.md documents pinning authelia.issuer and
jwksUrl, with the curl that reveals what the provider advertises for a given
Host; and the authentication failure event now carries a specific reason, so
id_token_issuer_mismatch is distinguishable from a signature failure or an
unreachable key set. The browser still learns nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 11:41:42 +02:00
d568b79223 Give the runtime real readiness, graceful shutdown and stated limits
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
Closes gap G08. /healthz returned a constant without probing anything, the server
called ListenAndServe with no signal handling, and the operational limits of
in-memory state, startup-loaded keys and local-only logout lived in code comments
rather than anywhere an operator would look.

/readyz probes LLDAP, Authelia and privacyIDEA; /healthz stays liveness and
probes nothing. Keeping them distinct matters: wiring liveness to dependency
health means an orchestrator restarts KeyCape when a dependency blinks, and a
restart also discards every in-flight login, so the reaction is worse than the
condition it reacts to.

LLDAP is probed with a bind rather than a dial, since a rotated or revoked
service password leaves the port open and every lookup failing -- exactly what
readiness should catch and exactly what a dial would miss. The response names the
failing check but never the reason: the endpoint is unauthenticated and upstream
error text carries hostnames and sometimes credentials-in-URLs. Results are
cached for 2s so an unauthenticated endpoint cannot be used to drive unbounded
upstream traffic, and probes run concurrently under a 3s bound so a hung
dependency makes the endpoint answer rather than hang with it.

SIGTERM and SIGINT now drain in-flight requests for 15s, under the 30s read/write
timeouts so a stuck request cannot outlive the window before SIGKILL.

docs/operations.md states the single-replica topology and why, and three limits
easy to get wrong: the constant key-1 key ID makes same-kid rotation a trap for
consumers caching JWKS, removing a client does not revoke its issued tokens, and
/logout is local only. No throughput figures are given, since nothing here
benchmarks KeyCape. Shared storage and refresh tokens stay excluded, as G08
allows.

Verified in the running executable: 503 naming all three checks failed while
/healthz returned 200, the LLDAP check flipping to ok once started, and 40/40
requests succeeding across a SIGTERM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
2026-09-08 09:43:49 +02:00