GET /api/v1/bgp/selection-funnel
GET /api/v1/bgp/selection-funnel?as_id=<uuid>&from_asn=64801&to_asn=65100
Selection funnel: for the prefixes whose live winning path traverses the observed adjacency from_asn→to_asn (path-adjacency scope — an AS_PATH neighbour pair, never a verified session), the RFC 4271 §9.1 step at which the best-path decision resolved: winner vs strongest runner-up, computed with the same comparator the engine's selection uses. Numeric steps carry the median winning margin. Self-checking: a prefix whose computed winner contradicts the stored is_best lands in undetermined — never a guess (a MED-non-transitivity pairwise loss also refuses this way). Live only — it reads bgp_rib_entry; there is no at= parameter, and the UI shows a refusal line on a historical cursor instead.
Auth: any authenticated user. Query: the as-graph scope selector (as_id, or network_id+asn; optional af, default ipv4), from_asn + to_asn (both required numeric; to_asn=0 — the fold column — is refused with 400: an aggregate carries no per-adjacency claim), limit (prefix cap, clamped ≤10000; truncated: true marks a lower bound).
Response 200 OK:
{
"from_asn": 64801, "to_asn": 65100, "prefixes": 42,
"steps": [
{ "step": "local_pref", "count": 30, "median_margin": 100 },
{ "step": "as_path_len", "count": 8, "median_margin": 1 },
{ "step": "only_path", "count": 3 }
],
"undetermined": 1, "truncated": false
}
step ∈ eligibility (§9.1.2 loop/next-hop ineligibility left it the only survivor), only_path (nothing to compare), local_pref, as_path_len, origin, med, ebgp_over_ibgp, igp_cost, router_id, tie (indistinguishable through the whole ladder). Steps with zero prefixes are omitted; the UI renders the block inside every adjacency card of the AS-flow lenses.