key-cape/workplans/KEY-WP-0030-tenant-precondition-guard.md
tegwick 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

73 lines
3 KiB
Markdown

---
id: KEY-WP-0030
type: workplan
title: "Make the static-registration precondition a checked condition"
domain: infotech
repo: key-cape
status: finished
owner: claude
topic_slug: tenant-precondition-guard
created: "2026-09-09"
updated: "2026-09-09"
state_hub_workstream_id: "9ed8f851-080a-5d15-8642-f799c2e3295c"
---
`informed-decision` replied on KEY-WP-0013-T05 and asked for one thing that is
ours to do: the caveat under which a registration-bound human tenant is safe
should be "a condition of the capability, so a future change to registration
policy has to confront it", not reasoning left in a message.
They are right that it was only prose. `docs/tenant-claim-contract.md` states it,
and a separate test asserted `registration_endpoint` is absent — but nothing
connected the two, so a future session adding dynamic registration would see a
test about discovery metadata, not a warning about relabelling users.
## Tie the exclusion to the capability it protects
```task
id: KEY-WP-0030-T01
status: done
priority: medium
state_hub_task_id: "4729d591-39d4-5f33-bef8-db8057090913"
```
`TestRegistrationBoundTenantRequiresStaticRegistration` asserts both halves
together: that a client-declared tenant is issued, and that dynamic client
registration is not advertised. Whichever is removed first, the failure points at
the other.
The failure message carries the reasoning rather than the observation. Adding
dynamic registration while a registration may declare its users' tenant means
anyone able to register a client can relabel the users who log in through it into
a zone of their choosing; the message says that and names the two ways out, drop
the capability or gate it to statically configured registrations.
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 deliberately not a vote on the tenant question. It makes the precondition
of one option checkable; it does not choose between them, and if option 1 lands
the capability and this guard are removed together.
## What stays with the owners
```task
id: KEY-WP-0030-T02
status: done
priority: medium
state_hub_task_id: "32b10327-f55d-5a0e-a4bc-be92929469dc"
```
`informed-decision` prefers option 2 and explicitly declines to treat that as the
answer, flagging it to gate-house on GH-DEC-2026-012 and naming approval-engine's
stake. KeyCape leans the same way, which is a reason for more care rather than
less: the code already implements option 2 (`329e48f`), so the doctrine question
is being decided against a working implementation. That is worth saying out loud
to everyone waiting on it, and was.
Their scope set `[openid, approval:read, approval:approve]` is confirmed and
already published. `client_id` and callback follow at their T07, deliberately not
before the tenant question resolves — registering a client that fails closed at
first use is the failure this exchange existed to avoid.