In this section

Separation of concerns

The system distinguishes two phases:

  1. Dispatch Validity: Is this candidate legal for this request?
  2. Ranking: Among valid candidates, which should be selected?

This document covers ranking. Dispatch validity is defined in Dispatch.

Ranking applies only to dispatch-valid candidates. Never rank before validating.


The valid set

Given a request r, define the valid set:

Valid(r) = { p ∈ C | Dispatch(p, r) }

Ranking is a total order over Valid(r).


Specificity distance

Definition

For a candidate p and request r:

dist(p, r) = spec_C(p) - spec_C(r)

Where spec_C is Cap URN specificity (see Specificity).

Interpretation

Distance Meaning Candidate Relationship
dist = 0 Equivalent Candidate matches request exactly
dist > 0 Refinement Candidate is more specific
dist < 0 Fallback Candidate is more generic

Preference order

Standard policy

The default ranking policy prefers:

  1. Exact match (dist = 0): Most preferred
  2. Refinement (dist > 0): Candidate specializes request
  3. Fallback (dist < 0): Candidate is generic, last resort

Within each category, prefer smaller absolute distance.

Formal ordering

For candidates a and b in Valid(r):

a ≺ b  (a preferred over b)  iff:
    dist(a,r) ≥ 0 ∧ dist(b,r) < 0           # a is refinement, b is fallback
  ∨ (dist(a,r) ≥ 0 ∧ dist(b,r) ≥ 0 ∧ dist(a,r) < dist(b,r))  # both refinements, a closer
  ∨ (dist(a,r) < 0 ∧ dist(b,r) < 0 ∧ |dist(a,r)| < |dist(b,r)|)  # both fallbacks, a closer

Simplified rule

preferred = min(valid_candidates, key=ranking_key)

def ranking_key(p, r):
    d = dist(p, r)
    if d >= 0:
        return (0, d)      # refinement tier, prefer smaller positive
    else:
        return (1, -d)     # fallback tier, prefer smaller negative

Tie-breaking

When multiple candidates have the same distance:

Default policy

First registered wins: the candidate registered first is selected.

Alternative policies

Some subsystems use different tie-breaking:

Subsystem Policy
UrnMatcher First by specificity, then by registration order
CapMatrix Strict > comparison (no ties possible)
CapBlock Strict > comparison (no ties possible)
RelaySwitch First registered wins

Avoiding ties

Design caps to avoid ties by ensuring distinct specificities:

  • Add distinguishing tags
  • Use consistent naming conventions
  • Register more specific caps before generic ones

Examples

Exact match preferred

Request: cap:in=media:pdf;extract;out=media:record
         spec = 3

Valid candidates:
  A: cap:in=media:pdf;extract;out=media:record     spec=3, dist=0
  B: cap:in=media:pdf;extract;out=media:record;v=2 spec=4, dist=+1
  C: cap:extract;in=media:;out=media:                                    spec=1, dist=-2

Ranking: A (dist=0) ≺ B (dist=+1) ≺ C (dist=-2)
Selected: A

Refinement when no exact match

Request: cap:convert;in=media:;out=media:
         spec = 1

Valid candidates:
  A: cap:in=media:pdf;convert;out=media:html   spec=3, dist=+2
  B: cap:in=media:image;convert;out=media:png  spec=3, dist=+2
  C: cap:convert;in=media:;out=media:                                spec=1, dist=0

Ranking: C (dist=0) ≺ {A, B} (dist=+2, tie)
Selected: C

If C not available:
Ranking: {A, B} tied at dist=+2
Selected: First registered of A or B

Fallback only

Request: cap:in=media:pdf;v=2.0;extract;out=media:record;format=json
         spec = 5

Valid candidates:
  A: cap:in=media:pdf;extract;out=media:record  spec=3, dist=-2
  B: cap:extract;in=media:;out=media:                                 spec=1, dist=-4

Ranking: A (dist=-2) ≺ B (dist=-4)
Selected: A (closer to request despite being less specific)

Preferred cap hints

User preference

The system supports an optional preferred cap hint:

RelaySwitch::route_request(request, preferred_cap: Option<&CapUrn>)

When preferred_cap is provided:

  1. Find valid candidates via is_dispatchable
  2. Among valid candidates, check if any is is_equivalent to preferred
  3. If found, select it regardless of specificity ranking
  4. Otherwise, fall back to normal ranking

Use cases

  • User explicitly requests a specific capability version
  • Configuration specifies a particular candidate
  • Testing with known handler

Properties

Determinism

Given the same:

  • Request
  • Set of registered candidates
  • Registration order

The selected candidate is deterministic.

Stability

Adding a new candidate P to the system:

  • If P is not in Valid(r), selection unchanged
  • If P is in Valid(r), P may be selected only if it ranks higher

Monotonicity

If candidate p is selected for request r, then p remains selected for any request r' where:

  • r' ⪯ r (r’ is more specific than r)
  • Dispatch(p, r') still holds

Implementation notes

Efficient selection

For small candidate sets, linear scan is sufficient:

fn select_best(candidates: &[CapUrn], request: &CapUrn) -> Option<&CapUrn> {
    let request_spec = request.specificity();

    candidates.iter()
        .filter(|p| p.is_dispatchable(request))
        .min_by_key(|p| {
            let dist = p.specificity() as isize - request_spec as isize;
            if dist >= 0 {
                (0, dist)
            } else {
                (1, -dist)
            }
        })
}

Caching

For frequently queried requests, cache:

  • The valid set Valid(r)
  • The sorted ranking
  • The selected candidate

Invalidate on candidate registration/unregistration.


Relationship to is_comparable

The is_comparable predicate from Predicates is sometimes used for discovery but not for ranking:

is_comparable(p, r)  ⟹̸  Dispatch(p, r)

Comparable candidates may not be dispatchable. Always check is_dispatchable first.


Summary

Concept Definition
Valid set { p | Dispatch(p, r) }
Distance spec_C(p) - spec_C(r)
Preference Exact (0) > Refinement (+) > Fallback (-)
Tie-break First registered (default)

Ranking is policy, not semantics. The dispatch predicate defines validity; ranking determines selection among valid options. Once a sequence of caps has been resolved into a workflow, that workflow is encoded as a graph and serialized via Machine notation.