Skip to content

Selection CredentialSelector: filter candidates by PD field ID values #4120

@stevenvegt

Description

@stevenvegt

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:

  1. 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.
  2. 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

  • NewSelectionSelector factory in vcr/pe/selector.go
  • Validates selection keys against PD field ids at construction — returns error for unknown keys
  • For input descriptors with matching selection keys: resolves field paths against candidates, filters by value equality
  • For input descriptors without matching selection keys: falls back to FirstMatchSelector
  • Zero matches after filtering → ErrNoCredentials (soft failure in matchConstraints, allows min: 0 submission requirements)
  • Multiple matches after filtering → ErrMultipleCredentials (hard failure)
  • Exactly one match → returns it
  • Multiple selection keys applied to same descriptor use AND semantics (all must match)
  • Test: single match success — selector picks the right credential
  • Test: zero matches returns ErrNoCredentials
  • Test: multiple matches returns ErrMultipleCredentials
  • Test: unknown selection key returns construction error
  • Test: no selection keys for a descriptor → fallback to first match
  • Test: multiple selection keys (AND semantics)
  • Test: multiple descriptors with independent selection keys
  • All existing PD matching tests pass unchanged

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions