Parent PRD
#4067 (see PRD comment)
What to build
A CredentialSelector implementation that filters candidates using the credential_selection parameter from the access token request. Each key in credential_selection maps to a field id in the PD's input descriptor constraints; the value narrows the match to credentials where that field equals the given value.
This replaces #4089 (DCQL selector). The CredentialSelector injection point from #4088 (PR #4098) is reused as-is.
Example
Given a PD with:
{
"id": "patient_id",
"path": ["$.credentialSubject.hasEnrollment.patient.identifier.value"],
"filter": { "type": "string" }
}
And a request with:
{ "credential_selection": { "patient_id": "123456789" } }
The selector resolves the patient_id field for each candidate VC and keeps only those where the value equals "123456789".
Design
NewSelectionSelector factory (vcr/pe/selector.go):
Follows the same pattern as NewDCQLSelector on branch feature/4089-dcql-selector:
- At construction: validate that each
credential_selection key matches a field id in the PD's input descriptors. Return error if any key doesn't match.
- Return a
CredentialSelector closure that, for each input descriptor:
- Finds which selection keys apply (fields with matching
id in this descriptor's constraints)
- If no selection keys apply to this descriptor → delegate to
FirstMatchSelector (fallback)
- Otherwise: resolve the field paths for each candidate, keep only candidates where all selected field values match
- Zero remaining → return
ErrNoCredentials
- Multiple remaining → return
ErrMultipleCredentials
- Exactly one → return it
func NewSelectionSelector(
selection map[string]string,
pd PresentationDefinition,
fallback CredentialSelector,
) (CredentialSelector, error)
Field value resolution: Reuse the existing matchField / getValueAtPath logic from presentation_definition.go to resolve field paths against candidates. The PD field's path array already points into the credential JSON.
Call stack
presenter.buildSubmission (vcr/holder/presenter.go)
→ pe.NewSelectionSelector(selection, pd, pe.FirstMatchSelector)
→ builder.SetCredentialSelector(selector)
→ builder.Build()
→ matchConstraints(vcs, selector)
→ selector(inputDescriptor, candidates)
→ resolve field paths, filter by values
Reusable code from existing branches
| Branch |
File |
What to reuse |
feature/4089-dcql-selector |
vcr/pe/selector.go |
Factory pattern, ID validation, fallback logic, error handling. Replace dcql.Match with field-value resolution. |
feature/4089-dcql-selector |
vcr/pe/selector_test.go |
Test structure: fallback, single match, zero matches (ErrNoCredentials), multiple matches (ErrMultipleCredentials), ID validation error, multi-descriptor independence. Adapt to use field IDs instead of DCQL queries. |
Acceptance criteria
Blocked by
Blocks
User stories addressed
- User story 1: select PatientEnrollmentCredential by patient ID
- User story 2: select HealthcareProviderTypeCredential by type
- User story 4: clear error on zero matches
- User story 5: clear error on multiple matches
- User story 6: simple key-value interface
- User story 8: backward compatible (fallback to first match)
Parent PRD
#4067 (see PRD comment)
What to build
A
CredentialSelectorimplementation that filters candidates using thecredential_selectionparameter from the access token request. Each key incredential_selectionmaps to a fieldidin the PD's input descriptor constraints; the value narrows the match to credentials where that field equals the given value.This replaces #4089 (DCQL selector). The
CredentialSelectorinjection point from #4088 (PR #4098) is reused as-is.Example
Given a PD with:
{ "id": "patient_id", "path": ["$.credentialSubject.hasEnrollment.patient.identifier.value"], "filter": { "type": "string" } }And a request with:
{ "credential_selection": { "patient_id": "123456789" } }The selector resolves the
patient_idfield for each candidate VC and keeps only those where the value equals"123456789".Design
NewSelectionSelectorfactory (vcr/pe/selector.go):Follows the same pattern as
NewDCQLSelectoron branchfeature/4089-dcql-selector:credential_selectionkey matches a fieldidin the PD's input descriptors. Return error if any key doesn't match.CredentialSelectorclosure that, for each input descriptor:idin this descriptor's constraints)FirstMatchSelector(fallback)ErrNoCredentialsErrMultipleCredentialsField value resolution: Reuse the existing
matchField/getValueAtPathlogic frompresentation_definition.goto resolve field paths against candidates. The PD field'spatharray already points into the credential JSON.Call stack
Reusable code from existing branches
feature/4089-dcql-selectorvcr/pe/selector.godcql.Matchwith field-value resolution.feature/4089-dcql-selectorvcr/pe/selector_test.goErrNoCredentials), multiple matches (ErrMultipleCredentials), ID validation error, multi-descriptor independence. Adapt to use field IDs instead of DCQL queries.Acceptance criteria
NewSelectionSelectorfactory invcr/pe/selector.goids at construction — returns error for unknown keysFirstMatchSelectorErrNoCredentials(soft failure inmatchConstraints, allowsmin: 0submission requirements)ErrMultipleCredentials(hard failure)ErrNoCredentialsErrMultipleCredentialsBlocked by
Blocks
User stories addressed