Parent PRD
#4067 (see PRD comment)
What to build
Inject a configurable credential selection strategy into the PD matcher, so that when multiple credentials match a single input descriptor, the selection logic determines which one is used — without changing the existing Match public API or return types.
Call stack
The credential selection flows through the following call stack:
API: RequestServiceAccessToken (auth/api/iam/api.go)
→ RequestRFC021AccessToken (auth/client/iam/openid4vp.go)
→ wallet.BuildSubmission (vcr/holder/sql_wallet.go)
→ presenter.buildSubmission (vcr/holder/presenter.go)
→ PresentationSubmissionBuilder (vcr/pe/presentation_submission.go)
→ builder.Build()
→ presentationDefinition.Match() (vcr/pe/presentation_definition.go)
→ matchBasic() or matchSubmissionRequirements()
→ matchConstraints()
Design
CredentialSelector type (vcr/pe/presentation_definition.go):
A function type that picks one credential from a list of candidates matching an input descriptor:
type CredentialSelector func(descriptor InputDescriptor, candidates []vc.VerifiableCredential) (*vc.VerifiableCredential, error)
FirstMatchSelector (vcr/pe/presentation_definition.go):
The default selector, extracted from the current matchConstraints logic. Picks the first matching credential, preserving existing behavior:
func FirstMatchSelector(_ InputDescriptor, candidates []vc.VerifiableCredential) (*vc.VerifiableCredential, error)
MatchWithSelector (vcr/pe/presentation_definition.go):
New method on PresentationDefinition that accepts a CredentialSelector. The existing Match method delegates to MatchWithSelector with FirstMatchSelector:
func (pd PresentationDefinition) Match(vcs []vc.VerifiableCredential) ([]vc.VerifiableCredential, []InputDescriptorMappingObject, error)
func (pd PresentationDefinition) MatchWithSelector(vcs []vc.VerifiableCredential, selector CredentialSelector) ([]vc.VerifiableCredential, []InputDescriptorMappingObject, error)
matchConstraints refactor (vcr/pe/presentation_definition.go):
Currently breaks on the first matching credential per input descriptor. Refactored to:
- Collect all matching VCs per input descriptor (remove the
break)
- Call the
CredentialSelector to pick one from the candidates
- Return the same
Candidate struct — no change to callers
PresentationSubmissionBuilder (vcr/pe/presentation_submission.go):
Add SetCredentialSelector method to the builder. When set, Build passes the selector through to MatchWithSelector. When not set, falls back to FirstMatchSelector:
func (b *PresentationSubmissionBuilder) SetCredentialSelector(selector CredentialSelector) *PresentationSubmissionBuilder
This allows the presenter layer (vcr/holder/presenter.go) to configure the selector when credential_query is provided, without changing the builder's existing API.
Future: DCQL selector (#4089)
The DCQL selector (implemented in #4089) will be a CredentialSelector that:
- Takes the
credential_query array as configuration
- For a given input descriptor, checks if a
credential_query with matching ID exists
- If yes: filters candidates using
dcql.Match, errors on zero or multiple matches
- If no: falls back to
FirstMatchSelector
The presenter layer will call builder.SetCredentialSelector(dcqlSelector) when credential_query is provided.
Acceptance criteria
Blocked by
None — can start immediately (independent of DCQL parser).
User stories addressed
- User story 11: existing behavior preserved for input descriptors without
credential_query
Parent PRD
#4067 (see PRD comment)
What to build
Inject a configurable credential selection strategy into the PD matcher, so that when multiple credentials match a single input descriptor, the selection logic determines which one is used — without changing the existing
Matchpublic API or return types.Call stack
The credential selection flows through the following call stack:
Design
CredentialSelector type (
vcr/pe/presentation_definition.go):A function type that picks one credential from a list of candidates matching an input descriptor:
FirstMatchSelector (
vcr/pe/presentation_definition.go):The default selector, extracted from the current
matchConstraintslogic. Picks the first matching credential, preserving existing behavior:MatchWithSelector (
vcr/pe/presentation_definition.go):New method on
PresentationDefinitionthat accepts aCredentialSelector. The existingMatchmethod delegates toMatchWithSelectorwithFirstMatchSelector:matchConstraints refactor (
vcr/pe/presentation_definition.go):Currently breaks on the first matching credential per input descriptor. Refactored to:
break)CredentialSelectorto pick one from the candidatesCandidatestruct — no change to callersPresentationSubmissionBuilder (
vcr/pe/presentation_submission.go):Add
SetCredentialSelectormethod to the builder. When set,Buildpasses the selector through toMatchWithSelector. When not set, falls back toFirstMatchSelector:This allows the presenter layer (
vcr/holder/presenter.go) to configure the selector whencredential_queryis provided, without changing the builder's existing API.Future: DCQL selector (#4089)
The DCQL selector (implemented in #4089) will be a
CredentialSelectorthat:credential_queryarray as configurationcredential_querywith matching ID existsdcql.Match, errors on zero or multiple matchesFirstMatchSelectorThe presenter layer will call
builder.SetCredentialSelector(dcqlSelector)whencredential_queryis provided.Acceptance criteria
CredentialSelectorfunction type defined and exported invcr/pe/presentation_definition.goFirstMatchSelectorextracted as an exported function — picks the first matching credentialmatchConstraintsrefactored to collect all matching VCs per input descriptor and call the selectorMatchWithSelectormethod added toPresentationDefinitionMatchdelegates toMatchWithSelectorwithFirstMatchSelector— no behavior changeSetCredentialSelectormethod added toPresentationSubmissionBuilderBuilduses the configured selector when set, falls back toFirstMatchSelectorCandidatestruct unchanged — still holds one*vc.VerifiableCredentialMatchpublic API return type unchangedPresentationSubmissionBuildertests pass unchangedBlocked by
None — can start immediately (independent of DCQL parser).
User stories addressed
credential_query