--- 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.