Commit graph

133 commits

Author SHA1 Message Date
e17b3f4631 Record deployed P06 policy and completed platform acceptance
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
2026-09-14 00:10:25 +02:00
187c49abe5 Pin final P04 release and native ownership-lock evidence
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 21:30:50 +02:00
a10c2f7f98 Record completed P04 recovery rollout and acceptance
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 21:19:13 +02:00
4d893ed3c2 Verify factor identity again after recovery mutation
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m6s
Authentication acceptance / provider-contract (push) Successful in 12s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 17:30:23 +02:00
3b7df9047e Implement audited lost-factor recovery and track remaining P04 acceptance
All checks were successful
Authentication acceptance / acceptance (push) Successful in 58s
Authentication acceptance / provider-contract (push) Successful in 14s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 17:28:45 +02:00
502a2d5c56 Retain provider acceptance CI and synchronized assurance task binding
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 17:06:32 +02:00
ecca6cb4ba Record verified enrollment assurance release and automate provider contract tests
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m13s
Authentication acceptance / provider-contract (push) Successful in 28s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 17:03:44 +02:00
122a0d1369 Require confirmed enrollment and genuine OTP evidence for MFA
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m10s
Build and Publish Container Image / build-and-push (push) Successful in 41s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 16:37:08 +02:00
74b4f7f1bc Establish scoped KeyCape factor custody and verified automatic renewal
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 16:25:33 +02:00
78f5a718e9 Record provider credential renewal release and remaining owner handoff
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 14:27:35 +02:00
632b1f1376 Support provider credential renewal and reject unsuccessful OTP validation
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m10s
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-13 14:23:24 +02:00
85f5deaf4a Support opt-in MFA per browser client with authoritative enrollment checks
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 00:28:09 +02:00
ac8ed65203 Support opt-in MFA per browser client with authoritative enrollment checks
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 40s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 00:27:28 +02:00
e1e292919a Record generated State Hub recovery task bindings
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 17:10:41 +02:00
62a86a4ba0 Record live account recovery and browser sign-out verification
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 10:50:35 +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
22788787c1 Record attended fresh-login rollout and verification
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 03:12:45 +02:00
0647301c03 Record published fresh-login image and attended rollout gate
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 02:53:55 +02:00
8d4336e944 Forward fresh-login requirements to the authentication provider
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 02:43:31 +02:00
139994cfac chore(consistency): register KEY-WP-0032 and refresh records [auto]
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-10 23:07:49 +02:00
f247d3529d Close T04 and pin the single route by which a principal becomes human
KEY-WP-0014-T04 sat `wait` with nothing outstanding, which is a contradiction.
Its title is admit rotation and verify consumer handoff, and both are delivered:
the semantics are published, the executor, authority and transport are named, the
CCR will be prepared on request, and step 4 shipped as keycape verify-client;
ops-warden took option (a) for the reason given, with both lanes owner-confirmed
and no route changed. Executing a rotation was never its deliverable, so treating
the owner's deferral as an open item would have kept the task open against work
it was not scoped to do. The deferral stands separately, revisited on evidence.

GH-DEC-2026-016 §5 applies A-16 to what makes a principal human. Verified against
source that `human` has exactly one route here -- a literal on the
authorization-code path after an upstream login resolved to a directory user,
with no configuration able to assert it -- so A-16 does not yet bite and no
provenance claim is warranted. Adding one would encode a distinction that does
not exist.

Asserting the behaviour would not protect that: a test checking a human token
says human passes just as happily when the value starts coming from a
registration. The guard parses the package and requires every principal_type
assignment to be a string literal, the set being exactly human and service. It
covers both shapes -- the browser path assigns into a map, the service path uses
a key-value pair in a map literal -- and checking only assignments found one of
two routes and passed, which the pinned literal set caught. Verified by mutation.

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-10 23:05:16 +02:00
c9bb7fac58 Register the approver client now its callback exists
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 47s
informed-decision submitted client_id informed-decision-approver and redirect
https://decisions.coulomb.social/auth/callback, with the origin already live and
verified by them rather than reported: both / and /auth/callback return 200 on a
Let's Encrypt certificate valid to 2026-12-09. The host is decisions, not the
decide of an earlier draft. Its path serves a placeholder for now, which does not
matter -- the redirect is matched as an exact string and never fetched.

Published as a public authorization_code client with S256 PKCE, audience
approval-engine, scopes openid/approval:read/approval:approve, mfaRequired true
and a declared tenant:platform. No secretRef, since PKCE is the whole proof.

TestApproverRegistrationShapeIsExact pins every field, so widening a scope or
relaxing MFA fails the build rather than reading as an edit, and asserts the
registration passes startup validation -- proving the KEY-WP-0028 tenant
exemption holds for the registration that actually depends on it.

Two existing guards fired on the way in and neither was loosened. The tenant pin
refused an unreviewed client carrying a tenant, which is its purpose, so the
approver was added to its reviewed set deliberately. And the audience test
panicked slicing secretRef[4:], an assumption that held while the fixture had
only confidential clients; the approver is the first public one, so the loop now
guards on the env: prefix.

The declared tenant reaches the token by the GH-DEC-2026-013 gap route by
construction, and tenant_source says so: registration, never directory.

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-10 22:50:13 +02:00
324b5e056d Record the owner decision: Qonto rotation deferred, not refused
The repository owner declined to schedule the rotation now. Recorded with the
reasoning rather than as a bare status so it is revisited on evidence instead of
re-litigated: nothing indicates compromise, the offer stands open, and one
founder-attended window is already pending for the fail-closed startup changes.

Names what should reopen it -- an actual or suspected exposure, the secret's age
becoming a stated concern, a consumer requiring proof of rotation, or a decision
to prove rotation step 4 before relying on it -- and says plainly that a calendar
date is not one of them.

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-10 09:15:48 +02:00
ed67780da8 Record the rotation authority answer and why it was not scheduled
The Qonto rotation blocker is narrowed from an open question to a schedulable
act: railiance-platform named the executor, transport and authority, and will
prepare the CCR if KeyCape asks. Not asked -- requesting a production credential
rotation with a restart window is the operator's decision, so it was put to him.

Established first that this rotation has no incident driver. The 2026-08-23
exposure covered credentials embedded in config.yaml and the signing key; the
rapp-qonto secret is an env: secretRef in a separate Secret and was not in that
payload. Lifecycle hygiene, not remediation -- which changes what a reasonable
window looks like, and means nobody should be carrying it as leftover incident
work.

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-10 08:10:34 +02:00
204bb229d6 chore(consistency): register KEY-WP-0031 [auto]
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-10 08:07:09 +02:00
f2c9aa45a8 Split the gate-house ruling work into its own workplan
fix-consistency skipped C-11 for two tasks I had appended to KEY-WP-0030 after
marking it finished, so they were never registered in the hub. The skip was
right: appending tasks to a finished workplan is the wrong shape, and the fix is
structural rather than flipping a status to get them registered.

The provenance implementation and the condition (b) correction are responses to
GH-DEC-2026-013, not to informed-decision's request for an enforced precondition.
They are now KEY-WP-0031, which also records that conditions (a) and (c) needed
nothing from this repository and why.

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-10 08:05:49 +02:00
7f6fdd00e6 Record the answered rotation authority; T04 now waits on a decision
railiance-platform answered the question ops-warden routed for us: rotation steps
1-2 are the platform operator's, attended, through the governed
openbao-platform-admin-login lane under founder_required OIDC. No unattended
agent, and they named themselves in that exclusion.

What remains is an artifact and a decision, not an open question. There is no
rotation CCR and they will not raise one speculatively, rightly, since a
two-custodian CAS rotation with a service restart is a distinct version-guarded
operation. Their offer is to prepare it if KeyCape asks, and ops-warden
deliberately did not ask on our behalf. So the task waits on the repository owner
deciding whether the rotation should happen and when -- nothing indicates
compromise, so it is hygiene plus the first real exercise of rotation step 4,
which is a judgement call rather than an obvious yes.

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-10 08:05:15 +02:00
63d646b594 Carry the tenant claim's provenance, and correct a guard the ruling voided
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 44s
GH-DEC-2026-013 §5 is a finding nobody asked for and ours to implement: tenant is
a bare string, so a consumer cannot tell a zone the directory asserted about the
person from one a registration supplied about the client they came through.
approval-engine exact-matches that string while its contract reads as though it
relies on the first -- the check is sound and the property a reader infers from
it is absent. gate-house named the property and left the mechanism to us.

Every token now carries tenant_source beside tenant: directory, registration or
default. Advertised in claims_supported, and asserted at the token level on both
grants rather than only in the resolution function, since the claim a consumer
reads is the thing under obligation.

Three values where the ruling names two, which is the judgement here. Labelling
an unasserted profile default as directory would reproduce the same defect one
level down -- a consumer reading an assertion the identity layer never made. The
ruling cites GH-DEC-2026-011 §3 on unknown versus absent for the case it
examined; the same rule applies to our own fallback. The agreement case resolves
to directory deliberately: if a registration declares the zone the directory also
assigned, the directory did assert it, and reporting the weaker source would
understate what is known.

Also corrects the guard shipped in 5f516a0. Its failure message offered two ways
out of adding dynamic registration, and condition (b) voids the second: admitting
dynamic registration voids the registration-bound shape that day, whatever state
the adapter is in. The message named an inadmissible resolution in the exact
place someone would read it while making that change.

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-10 07:58:29 +02:00
d29032d6c4 Record the operator withdrawal and the gate-house tenant conditions
approval-engine answered the presenting-actor question we put to them and the
answer withdrew the client: nobody presents approval-engine-operator. Nothing in
their repository obtains an OAuth token, and scope by scope the bundle never
described a single actor -- approve belongs to the human client, emit is
redundant, and no requester identity was ever settled for create. Annotated as
withdrawn in the example config and the packet rather than deleted, because they
asked for it to stay authenticable under CCR-2026-0018 if a presenter appears.
The separation-of-duties observation is kept because it constrains the
re-request: approve must not travel with the operational scopes.

Raised rather than filed: "do not provision" cannot undo a provision. The client
went live in the 2026-09-09 attended rollout, so the estate now holds a
confidential credential that can create and approve approvals, that nobody
presents, and that no consumer awaits. Standing capability with no counterparty
is a worse resting state than either provisioning it for a named presenter or
disabling it. Disablement is KeyCape's to execute and not KeyCape's to decide
alone, so it goes to the owners rather than into a commit.

Also records GH-DEC-2026-013 conditions (a) and (b) in the tenant contract, both
of which strengthen what we had written about ourselves. Row four is normative,
and in the direction we would not have guessed: a future change preferring the
DIRECTORY is also void, because "the directory won" is still a winner and picking
any winner turns a refusal into a silent cross-tenant assertion. And lifting the
dynamic-registration exclusion now VOIDS the registration-bound shape that day
rather than reopening it for review -- our "must be revisited" was too weak.
The shape is recorded as a declared bounded gap, not the terminal state.

Verification note: the Go suite could not be run green for this commit because a
peer session is mid-edit on token.go and human_tenant_test.go for the §5
provenance work. Every file in this commit is YAML or Markdown; both YAML files
were parsed and the example config's scope set and tenant were asserted unchanged.

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 23:30:09 +02:00
5f516a0fbb Make the static-registration precondition a checked condition
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 44s
informed-decision asked for the caveat under which a registration-bound human
tenant is safe to be a condition of the capability rather than reasoning in a
message, so a future change to registration policy has to confront it. They were
right that it was only prose: the contract stated it, a separate test asserted
registration_endpoint is absent, and nothing connected the two -- so a session
adding dynamic registration would have seen a test about discovery metadata, not
a warning about relabelling users.

The test asserts both halves together: that a client-declared tenant is issued,
and that dynamic registration is not advertised. Whichever is removed first, the
failure points at the other. The message carries the reasoning rather than the
observation -- anyone able to register a client could relabel the users who log in
through it -- and names the two ways out.

Verified in both directions rather than assumed: advertising a
registration_endpoint fails it with the escalation message, and neutering
humanTenant fails it with the message saying to remove the guard along with the
capability it protects.

This is not a vote on the tenant question. It makes one option's precondition
checkable; if option 1 lands, the capability and this guard are removed together.

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-09 23:23:48 +02:00
06f39906e9 chore(consistency): register KEY-WP-0029 [auto]
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-09 20:07:24 +02:00
d6ecb2b7aa Reassess SCOPE against the current revision
SCOPE.md declared itself reviewed against b989de4, 63 commits behind, with its
limits kept current by appending to bullets. That works until the header asserts
a review date that no longer describes what was reviewed -- the drift this file
exists to prevent, in the document whose only job is to be accurate.

Every claim was rechecked against source rather than carried forward: routes from
the mux registrations, subcommands from the dispatch, flags from the flag sets,
claim behaviour from token.go and userinfo.go. Three were false. Caller commands
omitted verify-client, the container was said to package only keycape (untrue
since KEY-WP-0026), and the issuer API row omitted /readyz. Capabilities that had
reached SCOPE only piecemeal are now in the capability table: the
authorization-code bindings, upstream token verification, readiness and drain, the
human tenant rules, authentication-time assurance, the shared verifier.

The substantive addition is a distinction SCOPE did not draw: source behaviour
and deployed behaviour are different claims with different evidence -- the test
suite and harness runs for one, committed receipts in docs/evidence/ for the
other. What is proven live is narrower and now stated exactly, including that the
issuer pin's pre-pin state did not match, so that risk was real rather than
hypothetical, and that the deployed image predates this revision so several
described behaviours are running nowhere yet.

That distinction is the fix for a repeated error: four times in two days a claim
about current state was written from a workplan paragraph or a partial view
instead of the authoritative artifact. A SCOPE that does not separate "the code
does this" from "this is running" invites the same mistake from the next reader.

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-09 20:06:38 +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
04931387f5 Record the stale-claim correction alongside the others
A message to railiance-platform listed the upstream issuer pin as outstanding
when the committed receipt showed it done ten hours earlier. Corrected to them
directly; recorded here with the other two instances because the pattern is the
point, not the individual slip. docs/evidence/ is the authoritative artifact for
live proofs in this repository, and reading it is cheaper than the corrections
it prevents.

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-09 16:41:10 +02:00
cb25ec2a8d Correct the T02 claim-contract record: the attended run already happened
A peer session recorded that T02's claim contract is unproven and waits on an
attended keycape verify-client run. The restraint behind that was right -- it
declined to read a client secret, which the provisioning packet does not admit --
but the conclusion was not: the attended run had already happened at
2026-09-09T00:10Z and its receipt is committed in this repo.

docs/evidence/2026-09-09-keycape-verifier-admission.json records, per client:
live_jwks_signature_verified, exact_claims_verified, excess_scope_denied and
wrong_secret_denied all true, lifetime 900s, run by a pinned verifier in an
attended owner process, with credential_values_emitted and
client_side_read_admitted both false. So it was run, and run the admitted way.
T02's status: done is correct and does not revert to wait.

The peer's paragraph is kept rather than deleted, with the correction appended
after it, so the record shows what was concluded and why it was wrong. The error
was reading "not admitted for me" as "not done by anyone" without checking
docs/evidence/ -- the third instance in two days of inferring repository state
from a partial view instead of reading it.

Also records what the receipt itself declines to claim, which nobody should
overstate later: real predecessor rotation and wall-clock expiry were not
exercised. Predecessor rejection is implemented and unit-tested in verify-client
but has never run against a real rotation, which remains KEY-WP-0014-T04 and
still has no admitted execution authority.

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:38:20 +02:00
1dc11b8448 Record live custody admission and re-verify the new rule against it
Read-only inspection shows KEY-WP-0013-T02's custody gate has moved since
yesterday: both approval clients are registered in sso/keycape-config, both
secrets exist in the namespace and are wired into the pod, and the image is
pinned to the digest T06 published. The live issuer passes every conformance
check that needs no credential.

That does not establish the claim contract. Subject, tenant, roles, scopes,
lifetime and excess-scope denial are what keycape verify-client proves, and it
needs the client secret in the environment. Reading either secret is client-side
retrieval, which the provisioning packet records as not admitted, so that run is
an attended operator action. T02 now waits on the run, not on custody.

Also re-verified the KEY-WP-0028 rule against the deployed config, after a peer
noted it fails closed at startup with a rollout imminent. The three browser
clients there carry neither serviceSubject nor roles; the three service clients
carry both legitimately. Kept as errors rather than warnings on that evidence.

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-09 14:44:38 +02:00
74b35b6107 Reject service-identity fields a browser client silently ignores
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s
serviceSubject and roles are read only on the client_credentials path. On a
browser client they are accepted and then ignored, since subject and roles come
from the directory user -- so a registration that looks effective fails later as
a downstream rejection rather than as a registration defect. tokenLifetime was
already rejected this way, so the rule existed and was incomplete.

Nothing in dev-config, the example fixture or the live deployment sets either
field on a browser client, checked against all three rather than assumed, so this
breaks no existing configuration.

tenant is deliberately excluded, and a test pins that: a browser client may
declare one, and humanTenant resolves it against the directory, refusing issuance
when they disagree (KEY-WP-0013-T05). An earlier version of this change rejected
tenant too and would have made that feature unusable. It started from T05's
blocker paragraph, which was accurate when written and already fixed by the time
this task began -- blocker prose ages faster than the code it describes.

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-09 14:42:26 +02:00
329e48f64a Let a human token carry the zone it is issued into, without relabelling anyone
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 46s
KEY-WP-0013-T05's tenant blocker did not need the decision it was waiting on. The
two proposed resolutions differ in where a human's tenant comes from -- the
directory record, or the client registration -- and an implementation exists that
is correct under either, so the choice can be made later without another
migration.

A client registration may now declare a tenant. humanTenant() resolves it by four
rules: no declaration keeps the directory answer unchanged; a declared zone
applies where the directory has placed the user nowhere; agreement passes; and a
declared zone conflicting with a directory assignment refuses issuance rather
than relabelling the user.

The refusal is the design, not an edge case. A registration can bind a zone for
unplaced users and can never move a placed one, so this gets the approval chain
its tenant:platform without writing a general cross-tenant override into the
issuer. It fails closed rather than picking a winner, because either answer would
be a silent cross-tenant assertion, and it reports 403 with
error_type: tenant_binding so an operator can tell a misconfigured registration
from a rejected login. If the owners later populate directory tenants, the same
code stops supplying the zone and starts enforcing agreement with it.

Safe only because client registrations are static and deployment-owned. The
tenant contract records that this rule must be revisited if dynamic client
registration is ever admitted.

Tests cover all four rules; neutering the conflict check fails the relabel test
rather than passing silently.

T05 now waits on one thing only: the client_id and callback URI from
informed-decision once it has a deployed origin.

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 14:40:36 +02:00
a73da29093 Answer the approver-client questions, and fix what checking them turned up
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
informed-decision and approval-engine both asked to hear problems with the human
approver registration now rather than at handover. Checking their requested shape
against the source rather than agreeing it on paper turned up three things.

Scope gap, accepted: [openid, approval:approve] cannot render a decision, since
GET /v1/approvals/{id} and /claim both need approval:read -- the surface could
submit an entry it was never able to display. Published
[openid, approval:read, approval:approve]. Reading through a service identity was
the alternative and is worse: it weakens the evidence-of-what-this-person-saw
claim the component exists to make. approval:consume stays excluded.

Assurance shape, published and a defect fixed. Both asked for a documented shape
and KeyCape already emitted one, so it is written down rather than renegotiated.
Writing it down surfaced that `at` was the token mint time rather than the
authentication time. Those differ by hours whenever a browser session is reused,
and approval-engine persists this object verbatim as the only downstream record
that MFA happened -- so a stored approval could have evidenced MFA at a moment
the person proved nothing. PKCESession.AuthTime now carries the original login
instant through session reuse, with mint time as the fallback.

Blocker found before anyone built on it: a human token cannot carry
tenant:platform. effectiveTenant resolves the human tenant from the directory
user, no adapter populates User.Tenant, and the per-client tenant field is read
only on the client_credentials path -- so every human token defaults to
tenant:coulomb, which approval-engine refuses by exact string equality. It would
have presented as a failed approval rather than a registration defect. Two
resolutions sent to the owners and neither implemented here: the choice decides
whether a human's tenant is a property of the person or of the registration, and
that is not KeyCape's alone to make.

Also recorded ops-warden's answers to KEY-WP-0014-T04, including their finding
that `warden plan` returns `autonomous` for a need containing generate and
CAS-write, because it has no read-versus-mutate intent. Their standing
instruction -- treat a warden plan verdict on any write, rotate or provision need
as unreliable until WARDEN-WP-0038 lands -- is recorded in the workplan rather
than left in an inbox.

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 14:25:38 +02:00
3a4fd38c00 docs: close live approval service registration gate
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-09 02:18:12 +02:00
cf23469ead Consume verified owner configuration pin before custody rollout
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-09 00:40:23 +02:00
e30ba7b3c0 docs(records): attach issuer proof to the receiving admission task
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-09 00:07:01 +02:00
41f6916a5d docs(identity): record verified live upstream issuer and completed cleanup
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 23:50:54 +02:00
7ecc78f410 chore(records): retain issuer probe task identity and index
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 23:26:46 +02:00
1b9793ffed docs(oidc): pin tested issuer probe and bounded owner execution packet
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 23:18:11 +02:00
6f33abddcf feat(oidc): prepare one-shot upstream issuer proof without token disclosure
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 23:14:46 +02:00
repo-manager
e147364721 repo.work.assign_missing_identifiers
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 17:04:19 +02:00
e534e1d5e8 docs(keycape): prepare pinned approval-client rollout and recovery
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 17:03:59 +02:00
dcebd46fa6 Require typed issuer refusals in live registration verification
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 40s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 16:46:02 +02:00
471465df22 Record the two ops-warden catalog corrections
Accepted ownership of lane key-cape-oidc-login, which their catalog has carried
as asked-and-waiting since 2026-08-28 -- they were blocked on us. Reported the
rapp-qonto-keycape-client blocker as stale against its own source-read date, and
asked for it to be narrowed to the two custody steps rather than cleared, since
the exchange and the step-3 verification now exist but successor generation and
the CAS write deliberately do not.

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-08 14:49:01 +02:00