Separation of concerns
The system distinguishes two phases:
- Dispatch Validity: Is this candidate legal for this request?
- 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:
- Exact match (
dist = 0): Most preferred - Refinement (
dist > 0): Candidate specializes request - 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:
- Find valid candidates via
is_dispatchable - Among valid candidates, check if any is
is_equivalentto preferred - If found, select it regardless of specificity ranking
- 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.