Context
ADCP #3690 security.mdx step 7 specifies the identity.key_origins consistency check is mandatory when the JWKS source was the operator brand.json and skipped for the specific (agent, purpose, role) tuple whose JWKS source was a publisher adagents.json signing_keys pin.
The check primitive landed in #775 as adcp.signing.check_key_origin_consistency(). It takes no source parameter — the docstring puts the carve-out on the caller. That's correct for the primitive, but Stage 5 needs the actual plumbing so the verifier integration can branch on the source.
What's missing
BrandJsonJwksResolver (and the upcoming verifier integration) need to expose which side of the chain resolved a given kid → JWK pair:
- brand_json source — the JWKS was fetched via brand.json's
agents[].jwks_uri. Consistency check applies.
- publisher_pin source — the JWKS was fetched via a publisher's
adagents.json signing_keys pin. Consistency check must be skipped for this tuple, otherwise legitimate publisher-pinned keys are wrongly rejected.
Acceptance
BrandJsonJwksResolver (or an adjacent type) exposes the source per resolved key — proposed shape: a new ResolvedKey dataclass returned alongside or in place of the bare JWK dict, with a source: Literal['brand_json', 'publisher_pin'] discriminant.
- The verifier integration in Stage 5 reads
source and gates the call to check_key_origin_consistency: call it on brand_json, skip on publisher_pin.
- Conformance test exercises both sources end-to-end — brand-json-sourced mismatch raises
request_signature_key_origin_mismatch, publisher-pin-sourced mismatch does NOT raise (since the check is skipped).
Why now (Stage 5, not later)
Without this, Stage 5's wire-up either (a) calls the check unconditionally and rejects legitimate publisher-pinned keys, or (b) silently never calls the check, leaving the operator-brand-json side unenforced. Either way breaks #3690 conformance.
References
🤖 Generated with Claude Code
Context
ADCP #3690 security.mdx step 7 specifies the
identity.key_originsconsistency check is mandatory when the JWKS source was the operator brand.json and skipped for the specific (agent, purpose, role) tuple whose JWKS source was a publisheradagents.json signing_keyspin.The check primitive landed in #775 as
adcp.signing.check_key_origin_consistency(). It takes nosourceparameter — the docstring puts the carve-out on the caller. That's correct for the primitive, but Stage 5 needs the actual plumbing so the verifier integration can branch on the source.What's missing
BrandJsonJwksResolver(and the upcoming verifier integration) need to expose which side of the chain resolved a givenkid→ JWK pair:agents[].jwks_uri. Consistency check applies.adagents.json signing_keyspin. Consistency check must be skipped for this tuple, otherwise legitimate publisher-pinned keys are wrongly rejected.Acceptance
BrandJsonJwksResolver(or an adjacent type) exposes the source per resolved key — proposed shape: a newResolvedKeydataclass returned alongside or in place of the bare JWK dict, with asource: Literal['brand_json', 'publisher_pin']discriminant.sourceand gates the call tocheck_key_origin_consistency: call it onbrand_json, skip onpublisher_pin.request_signature_key_origin_mismatch, publisher-pin-sourced mismatch does NOT raise (since the check is skipped).Why now (Stage 5, not later)
Without this, Stage 5's wire-up either (a) calls the check unconditionally and rejects legitimate publisher-pinned keys, or (b) silently never calls the check, leaving the operator-brand-json side unenforced. Either way breaks #3690 conformance.
References
docs/building/by-layer/L1/security.mdxstep 7🤖 Generated with Claude Code