[#4253 1/8] feat(pe): core selection engine Select + MatchReport#4280
Draft
stevenvegt wants to merge 1 commit into
Draft
[#4253 1/8] feat(pe): core selection engine Select + MatchReport#4280stevenvegt wants to merge 1 commit into
stevenvegt wants to merge 1 commit into
Conversation
Assisted-by: AI
Contributor
|
Coverage Impact This PR will not change total coverage. 🚦 See full report on Qlty Cloud »🛟 Help
|
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Parent PRD
#4253
Item 1 of 8. Foundation for the credential-selection rewrite. Also folds in the developer-diagnostics work from #4218 (this
MatchReportsupersedes the logging approach in closed PR #4225).Summary
Introduce the new pure matching engine
pe.Selectinvcr/pe, with structured diagnostics (MatchReport).Selectis consolidated: it owns the whole matching pipeline in three steps: (1) find which credentials match each descriptor's constraints and format; (2) search for a combination of credentials whose shared-idfield values agree; (3) apply the PD's submission-requirement rules (pick/all/min/max) to that combination, reusing the existinggroups()/apply()/deduplicate()helpers. No existing caller is changed in this PR; the engine is exercised entirely by a new black-box test suite. Item #3 then rewiresMatchontoSelectand deletesmatchConstraints/matchBasic/matchSubmissionRequirements/MatchWithSelectorand theCredentialSelectorabstraction.Implementation Spec
Surface (all new, in
vcr/pe)Bindingsismap[string]string, reusing the value stringification already inmatchesSelections(selector.go).Algorithm (three steps)
Step 1: match each descriptor on its own. For each input descriptor, evaluate every candidate credential via the existing
matchConstraint(presentation_definition.go:400) +matchFormat, with no cross-descriptor binding yet. Record pass/fail and, on fail, the reason (no value / filter / format). No new field-matching logic.Step 2: search for a consistent combination (depth-first over the matching credentials, in PD order):
WithInitialBindings; keys not present as field ids on this PD are dropped.pd.SubmissionRequirementswith a simple rule: with no submission requirements every descriptor is required; otherwise a descriptor is optional when its group can be satisfied without it.optional:truefield that resolves to no value contributes no binding.Step 3: apply the submission-requirement rules.
Selectpasses the step-2 combination to the existinggroups()/apply()/deduplicate()helpers (rule logic unchanged) to enforce the PD'spick/all/count/min/max/nested rules and produce the final selection; a descriptor dropped by a rule ends withVC=nil. The descriptor-map / JSON-path formatting thatmatchBasicandmatchSubmissionRequirementsdid is not part ofSelect; it moves to the thinMatchwrapper in item #3. The simple skip rule from step 2 can disagree with exact rule satisfaction in the deferredpick min>=1case; that limitation is documented and now lives in one place.Selection strategies
The engine always enforces binding consistency (P3), backtracking/optional-skip (P4), and unresolved-optional-doesn't-bind (P6). The strategy controls only what happens when more than one complete, binding-consistent assignment exists:
FirstMatch(default): return the first assignment. This is the lenient, backward-compatible behavior. For any PD that does not reuse anidacross descriptors it reproduces today's matcher exactly, includingNewFieldSelector'sErrMultipleCredentialswhen a caller-bound descriptor still resolves to more than one credential.Strict: a second complete consistent assignment isErrMultipleCredentials(Policy 5), naming the ambiguous descriptors so the caller can add disambiguating selection keys.StrictsupersedesFirstMatch.Policy 5 (ambiguity-as-error) is the behavior-risk to be aware of. It is the only rule here that can turn a previously-succeeding match into an error: a descriptor with several equally-valid credentials that the caller did not disambiguate is silently first-picked today but errors under
Strict. That is exactly why it is gated behind the strategy rather than always on. By contrast Policy 3 (same-idagreement) is always on but safe — it only affects PDs that reuse anidacross descriptors, which is a new pattern with no deployed users.Strategy is an input, not parsed here (scope)
Selecttakes aSelectionStrategyvalue and honors it. How that value is chosen is out of scope for this PR. The intent is to declare it per-PD at the config layer (policycredentialProfileConfig/ discoveryServiceDefinition), defaulting toFirstMatch("loose") and flipped toStrictin a future major release — but parsing and registration of that config land in items #5/#7. This PR only defines the enum and makes the engine behave correctly for each value.MatchReport (diagnostics; supersedes closed PR #4225)
Populated only under
WithSelectionTrace()(Messagerendered lazily, so a non-trace run pays nothing). Trace depth: step-1 (per-descriptor) dismissals recorded fully; binding conflicts recorded on the decisive path (the chosen assignment, or the conflicts that caused failure/ambiguity), not every backtracking visit. No surface (endpoint/UI) in this PR — consuming the report is a follow-up to #4218.Testing
Black-box
select_test.go, no mocks or on-disk fixtures. Two buckets:FirstMatch+bindings reproduces theTestNewFieldSelectormatrix (single/multi-field AND, multi-descriptor, type conversions string/float64/bool, zero-match -> unfilled);FirstMatchwith no bindings reproducesFirstMatchSelector; an all-required PD reproducesmatchBasic(every descriptor must fill, elseErrNoCredentials); a representative submission-requirements PD reproducesmatchSubmissionRequirements(all, andpickwithmin/max);ErrNoCredentialscases.MatchReportcontents for a dismissed VC and for an ambiguity; the two-VP composition pattern (twoSelectcalls chained through a bindings filter).Exhaustive submission-requirement rule cases stay in
submission_requirement_test.go(theapply()logic is unchanged) and the integration-levelTestMatchsuites; #1 adds only representative satisfaction characterization, not a full duplication.Acceptance Criteria
Selectwith functional options as specified;ResultreusesCandidate.Strict;FirstMatchis the default.FirstMatchreproduces bothFirstMatchSelectorandNewFieldSelectorbehavior.Selectapplies submission-requirement rules by reusinggroups()/apply()/deduplicate(); whether a descriptor may go unfilled is derived frompd.SubmissionRequirements.FirstMatchalso reproducesmatchBasic(all-required) andmatchSubmissionRequirements(all/pick/min/max).MatchReportpopulated only underWithSelectionTrace(), with the specified reasons and decisive-path conflict narration.vcr/pe; existingvcr/petests still green.go build ./...andgo test ./vcr/pe/...pass.