73 lines
2.9 KiB
Markdown
73 lines
2.9 KiB
Markdown
|
|
---
|
||
|
|
id: KEY-WP-0032
|
||
|
|
type: workplan
|
||
|
|
title: "Pin the single route by which a principal becomes human"
|
||
|
|
domain: infotech
|
||
|
|
repo: key-cape
|
||
|
|
status: finished
|
||
|
|
owner: claude
|
||
|
|
topic_slug: principal-type-provenance-guard
|
||
|
|
created: "2026-09-10"
|
||
|
|
updated: "2026-09-10"
|
||
|
|
---
|
||
|
|
|
||
|
|
GH-DEC-2026-016 §5 applies A-16 to what makes a principal *human*: if `human` is
|
||
|
|
reachable by two routes — asserted by the identity layer about the person, or
|
||
|
|
supplied by a registration about the client they came through — the record must
|
||
|
|
say which, and a human-in-the-loop control must not be discharged on the
|
||
|
|
registration-supplied one. Refusing a service principal while accepting an
|
||
|
|
unverified assertion of humanity moves the defect rather than closing it.
|
||
|
|
|
||
|
|
gate-house said explicitly this was not a request. It is built because the
|
||
|
|
property it protects is true *today* and is the kind a plausible change erases
|
||
|
|
quietly.
|
||
|
|
|
||
|
|
## Pin the property, not the behaviour
|
||
|
|
|
||
|
|
```task
|
||
|
|
id: KEY-WP-0032-T01
|
||
|
|
status: done
|
||
|
|
priority: medium
|
||
|
|
```
|
||
|
|
|
||
|
|
`principal_type` has exactly one route to `human`: a string literal on the
|
||
|
|
authorization-code path, reached only after an upstream login resolved to a
|
||
|
|
directory user. No configuration field can assert it — verified against source
|
||
|
|
rather than assumed, and it is why the tenant provenance work has no counterpart
|
||
|
|
here and why A-16 does not yet bite.
|
||
|
|
|
||
|
|
Asserting the *behaviour* would not have protected that. A test checking "a human
|
||
|
|
token says human" passes just as happily when the value starts coming from a
|
||
|
|
registration. So the guard parses the package and requires every assignment to
|
||
|
|
`principal_type` to be a string literal, with the literal set exactly
|
||
|
|
`{human, service}` — one route each. `claims["principal_type"] =
|
||
|
|
client.PrincipalType` would look like a feature in review and fails the build
|
||
|
|
instead, with a message saying that if it is deliberate the claim must carry its
|
||
|
|
provenance the way `tenant_source` does.
|
||
|
|
|
||
|
|
Both assignment shapes are covered. The browser path assigns into a map; the
|
||
|
|
service path uses a key-value pair inside a map literal. Checking only
|
||
|
|
assignments found one of two routes and passed — a guard proving less than it
|
||
|
|
claimed, caught because the expected literal set was pinned rather than merely
|
||
|
|
counted.
|
||
|
|
|
||
|
|
Verified by mutation: replacing the literal with a function of the client
|
||
|
|
registration fails it at the exact line, and restoring it passes.
|
||
|
|
|
||
|
|
## What this deliberately does not do
|
||
|
|
|
||
|
|
```task
|
||
|
|
id: KEY-WP-0032-T02
|
||
|
|
status: done
|
||
|
|
priority: medium
|
||
|
|
```
|
||
|
|
|
||
|
|
No `principal_type_source` claim was added. There is one route, so a provenance
|
||
|
|
marker would encode a distinction that does not exist and invite consumers to
|
||
|
|
branch on it — the opposite of the tenant case, where two routes existed and the
|
||
|
|
claim could not say which had been taken.
|
||
|
|
|
||
|
|
The guard is the honest response to "not a request today": it makes the absence
|
||
|
|
of a second route a checked fact rather than a remembered one, and hands the
|
||
|
|
decision to whoever creates the second route, at the moment they create it.
|