--- id: KEY-WP-0028 type: workplan title: "Reject service-identity fields that a browser client silently ignores" domain: infotech repo: key-cape status: finished owner: claude topic_slug: browser-client-field-validation created: "2026-09-09" updated: "2026-09-09" --- `serviceSubject` and `roles` are read only on the `client_credentials` path. On a browser client they are accepted at startup and then ignored: the subject and roles come from the directory user. A registration that looks effective and is not fails later, somewhere else, as a downstream rejection rather than as a registration defect. `tokenLifetime` was already rejected this way for non-service clients, so the rule exists — it was just incomplete. ## Reject the ignored fields ```task id: KEY-WP-0028-T01 status: done priority: medium ``` Config validation now rejects `serviceSubject` and `roles` on any client that does not use `client_credentials`, naming where the value actually comes from. Nothing in `config/dev-config.yaml`, the example fixture or the live deployment sets either field on a browser client, so this breaks no existing configuration — checked against all three rather than assumed. **`tenant` is deliberately excluded from the rule**, and a test pins that. A browser client may declare a tenant; `humanTenant` resolves it against the directory and refuses issuance when the two disagree (KEY-WP-0013-T05). An earlier version of this change rejected `tenant` too, which would have made that feature unusable. ## Correction ```task id: KEY-WP-0028-T02 status: done priority: medium ``` This work started from KEY-WP-0013-T05's blocker paragraph, which described a human token as unable to carry `tenant:platform`. That was true when written and had already been fixed in a concurrent session by the time this task began; the paragraph was read as current state rather than as a dated record. Two consequences, both corrected within the session: - the `tenant` rejection was removed before it was committed, and a test now asserts a browser client carrying `tenant: tenant:platform` validates cleanly; - a hub decision (`0145ab57`) recording the tenant question as open and unimplemented was withdrawn with a rationale pointing at the implementation. Nothing was built on it. The lesson worth keeping: in a repository where several sessions work at once, blocker prose ages faster than the code it describes. Re-read the source before acting on a workplan's description of it.