informed-decision/docs/keycape-client-registration.md
tegwick 4d33a59f5b docs(T07): settle the approver origin as decisions.coulomb.social
The operator assigned decisions.coulomb.social, not the decide.coulomb.social
this workplan proposed. Redirect URIs match byte-exactly at /authorize, so the
proposal is corrected in place rather than reconciled after submission — a
one-character miss presents as a rejected approval, not as a registration
defect.

DNS resolves. The Ingress and certificate are written in railiance-apps
(manifests/informed-decision-ingress.yaml, dry-run clean) but not applied, so
T07 stays progress: DNS alone is not an origin, and a host that does not answer
over TLS fails the same way a wrong hostname does, only later.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2072522@bnt-lap001
Assistant-Session: 46173adf-7302-4ede-99d6-963b61359928
2026-09-10 16:17:52 +02:00

7.7 KiB

OIDC browser-client registration — informed-decision approver surface

Workplan task: INFD-WP-0001-T07 For: key-cape (KEY-WP-0013-T02, KEY-WP-0013-T05) Status: draft — not yet submitted. One input outstanding: the deployed origin (§2). Everything else is fixed and stable. Rulings: GH-DEC-2026-012, GH-DEC-2026-013 Contracts cited: key-cape/docs/approval-engine-auth-contract.md, key-cape/docs/tenant-claim-contract.md, approval-engine/docs/approver-surface-requirements.md


1. What this closes

On 2026-09-08 key-cape asked approval-engine for the human approver client's client_id and full callback URI. approval-engine correctly declined to invent them: it is a bearer-token resource server with no Ingress, no redirect endpoint, and no browser client. It recorded that the two strings belong to whichever browser-facing approver UI presents approval:approve tokens to it.

That component is this repository. These are the two strings.


2. The two strings

Field Value
client_id informed-decision-approver
Redirect URI https://decisions.coulomb.social/auth/callback

client_id is fixed and stable. It will not change.

The origin is settled. 2026-09-10: the operator assigned decisions.coulomb.social to this surface. It is not the decide.coulomb.social this document proposed — the assigned name is the one that counts, and it is recorded here rather than the proposal, because redirects match exactly at /authorize and a near-miss fails closed. That exactness is why approval-engine refused to guess in the first place, and it is the reason the proposed name is corrected here before submission rather than reconciled afterwards.

  • Assigned: decisions.coulomb.social, giving https://decisions.coulomb.social/auth/callback.
  • Consistent with the estate's existing pattern (forgejo.coulomb.social, hub.coulomb.social, kc.coulomb.social), Traefik ingressClassName, TLS secret per host.
  • DNS resolves to the cluster address as of 2026-09-10.
  • Still requires, in railiance-apps rather than here: an Ingress manifest and an issued certificate. Manifest written 2026-09-10 (manifests/informed-decision-ingress.yaml); not yet applied.

This document is submitted to key-cape once the host answers over TLS — not before. DNS alone is not the origin; an origin that does not complete a TLS handshake fails the same way a wrong hostname does, only later. The path component /auth/callback was fixed ahead of the host, so nothing else in this registration moves.


3. Client shape

Property Value
Grant Authorization code
PKCE Required, S256
Client type Public
Redirect matching Exact
Scopes openid, approval:read, approval:approve
mfaRequired true

approval:read is required and was missing from the original specification. Without it this surface could submit an approval entry it was never able to display — raised by approval-engine, confirmed by key-cape, now in their published contract.

approval:consume is never requested. approval-engine refuses it for human principals, and consumption belongs to the PEP that causes the side effect (GH-DEC-2026-003).


4. Expected access token

Verified by approval-engine (RS256 against the issuer's /jwks):

Claim Value
aud approval-engine — the resource server, never the OAuth client id
tenant tenant:platformexact string equality, no alias or prefix handling
principal_type human
sub non-empty; becomes the approver identity in the entry
scope non-empty, containing the exact scope for the route
assurance a non-null JSON object — shape below
iss, exp, iat verified, 30s leeway

The access token is the evidence. id_token is never presented to approval-engine and appears nowhere in that codebase.

assurance

The shape is key-cape's, defined in their docs/approval-engine-auth-contract.mdlevel (aal1|aal2), methods, mfa, source, at. Cited here rather than restated, so it cannot drift.

Two properties matter to this surface and are recorded rather than assumed:

  • at is the authentication time, not the token mint time. key-cape fixed a defect here on 2026-09-09; the two differ by hours when a browser session is reused, and approval-engine stores this object verbatim as the only downstream record that MFA happened. Freshness is judged by comparing at, never by assuming it.
  • key-cape emits assurance on every human token and never empty. This surface nevertheless requires the object to carry authentication facts rather than relying on it always arriving populated — approval-engine accepts an empty object, so an empty one would produce an entry evidencing nothing.

5. Tenant provenance — declared, not assumed

This registration declares a tenant the directory record does not carry, and says so here rather than letting it read as a directory-asserted fact.

GH-DEC-2026-013 ruled directory-sourced tenancy the terminal state and granted key-cape's registration-bound shape (329e48f) as a declared bounded gap — admissible because its distinguishing case fails closed: registration and directory disagreeing refuses issuance with 403 tenant_binding rather than relabelling.

Nothing populates domain.User.Tenant for approver users. So a registration declaring tenant:platform reaches the token by the gap route by construction, not by accident. Gate House was explicit: do not declare a tenant the directory does not carry unless prepared to say so in the record.

Consequences accepted here:

  1. This is transitional. When the directory carries tenants, the same key-cape code stops supplying and starts enforcing agreement, with no second migration. This registration is expected to survive that unchanged.
  2. The claim is stored with its provenance. key-cape emits tenant as a bare string, so a consumer cannot distinguish a directory-asserted tenant from a registration-supplied one. Until the claim carries its own provenance (GH-DEC-2026-013 §5, second field deliberately undesigned), this surface records which route the value arrived by. See PR-09.
  3. The tenant claim is never used as the act-scope. binding.target is the act-scope and is committed inside view_hash. Two different facts — which scope this act enters, and which tenant this person belongs to — must not share one field here just because they share one upstream. See EvidenceModel.md §8c and PR-08.

6. What this surface does with the token

  • GET /v1/approvals/{id} and /claimapproval:read, to render.
  • POST /v1/approvals/{id}/entriesapproval:approve, to record a binding.
  • Never /consume.
  • Never any path containing check or ending /authorize — they 404 by design, and there is no "may this person approve" surface to call.

A 200 from approval-engine is not entitlement to view. A decision is obtained from access-engine before an approval is rendered to a person (PR-05) — noting that decision attributability is a declared open gap (GH-DEC-2026-010, FLEX-WP-0024) and this path is not described as validated while it stands.


7. Outstanding

# Item Owner
1 DNS A record (done 2026-09-10) + Ingress + TLS for decisions.coulomb.social railiance-apps / deployment
2 Submit this document to key-cape, closing KEY-WP-0013-T02 this repo, once 1 lands
3 Prove a token issued against the registration is accepted by approval-engine T08