Parent: #11 (finding 4). Verified against auth 4ea0484, auth.policy-verifier 73cebc6, auth.provider f49658a3.
Problem
The provider↔verifier contract is written down only at signature mechanics (alg/JWKS symmetry). The claim-level half — "claim X, written by the provider at P, is consumed by the verifier as Y" — exists only scattered: the verifier maps its side well (e.g., ResourceActionScopeRuleCollector's capability-ceiling comment), the provider's side lives in two CHANGELOG asides (CHANGELOG.md:463-469 iss pinning, :471-475 allowedScopes ceiling), and no durable document holds the correlation. The umbrella is the stack-level doc home (README.md:3), so the contract belongs here.
Plan
1. New docs/claims-contract.md (architecture.md is a short component overview whose sections all have .ja.md twins; a ~10-row table with per-claim prose would dwarf it). Link from architecture.md's Auth Flow section and from README. One row per claim that crosses (or pointedly does not cross) the boundary — all rows below are code-verified; anchor rows to stable names (collector class, config key), keep file:line as secondary hints:
| Claim |
Provider writes |
Verifier reads |
Semantics |
scope |
oauth/src/grants/authorization.mts:454,508 — space-joined string or omitted; ceiling = client allowedScopes |
PayloadScopeCollector → ATTR_SCOPES; ResourceActionScopeRuleCollector:94 demands {action}:{resourceType} |
capability ceiling, not a grant |
sub |
authorization.mts:338-400 (user id, never session id); core token.mts:125 |
PayloadSubjectIdCollector → ATTR_USER_ID; verify.mts:525 echoes as subject |
identity travels intact |
azp |
authorization.mts:506-507 (authenticated client, not raw body) |
PayloadSubjectIdCollector → ATTR_CLIENT_ID |
requesting client |
aud |
RFC 8707 resource → audience, authorization.mts:408-449; dropped on refresh without resource |
jose pin tokenAuthenticator.mts:120,477; oauth.jwt.audience |
token addressed to this resource server |
iss |
token.mts:123; required, never request-derived (CHANGELOG:463-469) |
jose pin tokenAuthenticator.mts:118,476; oauth.jwt.issuer |
deployment identity pin |
typ (header) |
at+jwt :509 / rt+jwt :524 / id+jwt idToken.mts:59 |
oauth.jwt.tokenType default at+jwt |
the ONLY discriminator between token kinds |
exp/iat |
token.mts:120-122 |
both required, tokenAuthenticator.mts:69-72; maxTokenAgeSeconds caps issuer's exp |
lifetime |
jti |
token.mts:121; provider-side replay detection |
not read by the verifier |
one-sided claim — stated so |
groups |
only via scope-gated claim filter into userinfo/id_token (claimFilter.mts:37), NOT the access token |
only from request context (RequestContextAttributeCollector) |
does NOT cross the AT boundary today — recorded to prevent assumed coupling |
Each row also cites the existing in-repo mapping comment it consolidates (token-flow/abac assertions, verifier schema comments) so the doc is an index over the house's scattered mappings, not a new parallel truth.
2. Pointer edits so both sides can find it (separate PRs in each sibling; umbrella doc must merge first or the links 404):
- auth.provider
docs/design-vocabulary.md:53-56 ("Cross-repo contracts" bullet — currently delegates to the workspace-local Dependency_map.xml, which is not on GitHub): append the link to this doc.
- auth.policy-verifier
README.md "Connecting to auth.provider" (:370): one lead-in line linking this doc for claim-level semantics.
Notes / risks
- docs/ convention keeps
.ja.md twins — plan claims-contract.ja.md or the convention breaks.
- file:line refs rot as siblings move; stable-name anchoring above is the mitigation.
- The E2E already pins several rows executable (
typ triple at token-flow:87-94, scope string-not-array at :78-85); the doc should cite those tests as its enforcement arm rather than inventing new ones.
Parent: #11 (finding 4). Verified against auth 4ea0484, auth.policy-verifier 73cebc6, auth.provider f49658a3.
Problem
The provider↔verifier contract is written down only at signature mechanics (alg/JWKS symmetry). The claim-level half — "claim X, written by the provider at P, is consumed by the verifier as Y" — exists only scattered: the verifier maps its side well (e.g., ResourceActionScopeRuleCollector's capability-ceiling comment), the provider's side lives in two CHANGELOG asides (CHANGELOG.md:463-469
isspinning, :471-475allowedScopesceiling), and no durable document holds the correlation. The umbrella is the stack-level doc home (README.md:3), so the contract belongs here.Plan
1. New
docs/claims-contract.md(architecture.md is a short component overview whose sections all have.ja.mdtwins; a ~10-row table with per-claim prose would dwarf it). Link from architecture.md's Auth Flow section and from README. One row per claim that crosses (or pointedly does not cross) the boundary — all rows below are code-verified; anchor rows to stable names (collector class, config key), keep file:line as secondary hints:scopeoauth/src/grants/authorization.mts:454,508— space-joined string or omitted; ceiling = clientallowedScopesPayloadScopeCollector→ ATTR_SCOPES;ResourceActionScopeRuleCollector:94demands{action}:{resourceType}subauthorization.mts:338-400(user id, never session id);core token.mts:125PayloadSubjectIdCollector→ ATTR_USER_ID;verify.mts:525echoes assubjectazpauthorization.mts:506-507(authenticated client, not raw body)PayloadSubjectIdCollector→ ATTR_CLIENT_IDaudresource→ audience,authorization.mts:408-449; dropped on refresh withoutresourcetokenAuthenticator.mts:120,477;oauth.jwt.audienceisstoken.mts:123; required, never request-derived (CHANGELOG:463-469)tokenAuthenticator.mts:118,476;oauth.jwt.issuertyp(header)at+jwt:509 /rt+jwt:524 /id+jwtidToken.mts:59oauth.jwt.tokenTypedefaultat+jwtexp/iattoken.mts:120-122tokenAuthenticator.mts:69-72;maxTokenAgeSecondscaps issuer's expjtitoken.mts:121; provider-side replay detectiongroupsclaimFilter.mts:37), NOT the access tokencontext(RequestContextAttributeCollector)Each row also cites the existing in-repo mapping comment it consolidates (token-flow/abac assertions, verifier schema comments) so the doc is an index over the house's scattered mappings, not a new parallel truth.
2. Pointer edits so both sides can find it (separate PRs in each sibling; umbrella doc must merge first or the links 404):
docs/design-vocabulary.md:53-56("Cross-repo contracts" bullet — currently delegates to the workspace-localDependency_map.xml, which is not on GitHub): append the link to this doc.README.md"Connecting to auth.provider" (:370): one lead-in line linking this doc for claim-level semantics.Notes / risks
.ja.mdtwins — planclaims-contract.ja.mdor the convention breaks.typtriple at token-flow:87-94, scope string-not-array at :78-85); the doc should cite those tests as its enforcement arm rather than inventing new ones.