SIP Trunk Call Routing — Generic Flow & Migration Ground Rules

Draft v2

Status

Second draft. Structurally grounded in a full parse of the source config, validated against ~1,000 real production traces plus one complete, hop-by-hop-confirmed end-to-end call, and now confirmed against the actual inbound signaling-type discriminator (see §1). Behavioral coverage is still narrower than the structure: we have direct evidence of exactly one Destination Resolution pattern in action (sequential failover into a SIP trunk). Treat the shape of this document as solid and the specific behavioral claims as best-current-understanding, to be corrected as more traces arrive.

Scope assumption for this draft (v2 change)

Every outbound leg — whether it currently terminates on a true external SIP trunk or on what the source system models as an ISUP/TDM circuit — is treated uniformly as "hand off to a SIP proxy (customer or vendor)." This assumes a SIP-facing gateway exists (or will exist) wherever the source system currently seizes a TDM circuit directly, so the outbound side of Destination Resolution needs no SIP/ISUP fork at all — every terminal outcome is "select a proxy target," full stop. The only real scope boundary left is on the inbound side: calls that arrive via ISUP signaling are ignored entirely; only SIP-signaled inbound calls are in scope. That boundary turns out to be a single, explicit, cleanly-checkable attribute — see below.

1. The generic flow

flowchart TD
    A[Call arrives on a trunk] --> F{Incoming signaling stack?}
    F -->|ISUP| X[Out of scope — ignored entirely]
    F -->|SIP| B[Number Classification & Attribute Tagging]
    B -->|reject| R1[Call rejected]
    B -->|success| C[Destination Resolution]
    C -->|reject| R2[Call rejected]
    C -->|resolves| S[Hand off to a SIP proxy — customer or vendor]
    

The inbound filter is a single explicit attribute, not an inference. The source config has a first-class field — effectively "incoming media signaling stack" — with values like SIP and ISUP, checked directly as a match condition. We found it in active use at exactly the point you'd hope: right at the trunk-call entry table and one of its near-immediate neighbors ("branch, if in from ISUP Tandem switches"), where entries explicitly match Signaling Stack = ISUP (paired with the specific incoming ISUP circuit) to give ISUP-tandem-originated calls different downstream handling. This means the SIP/ISUP inbound split is early, explicit, and localized — not something requiring deep inference from call outcomes. A pre-filter or ingress check on this same attribute is a legitimate, low-risk way to decide "is this call even in scope" before any further logic runs.

Stage 1 — Number Classification & Attribute Tagging (source system: Number Validation). A chain of pattern-match steps over the calling and/or called number. Each step either:

Stage 2 — Destination Resolution (source system: Trunk Routing). Always entered from the same single shared starting point, regardless of which path Stage 1 took to get there. A chain of steps that either:

Stage 2 terminates by selecting a SIP proxy target, or by rejecting.

2. Reusable decision primitives

PrimitiveWhat it doesConfirmed in a real trace?
Pattern match with precedenceLongest explicit match wins; if none, longest wildcard match wins; ties broken by attribute specificity, then lowest config indexStructurally yes (~1,000 traces); precedence tie-breaking specifically not yet exercised in a sample
Signaling-stack filterMatch on incoming call's signaling type (SIP vs ISUP) as an explicit attributeYes — found in active use, paired with specific circuit identifiers
Attribute taggingA step sets a value that a later, distant step readsYes — DID-screening result set early, read much later to pick a specific proxy route
Deterministic chainingStep A always leads to step BYes, extensively (every trace so far)
Sequential failover setTry option 1; if it dead-ends, try option 2; etc., in a fixed, meaningful orderYes — the one full trace we have is exactly this pattern
Weighted-random setPick among options by configured probabilityNot yet — present in the config, not yet seen fire in a trace
Bulk file-based lookupA single step matching against a large external list (e.g. per-carrier DID ranges) rather than a handful of hand-authored rulesYes

3. Migration ground rules (invariants to preserve)

  1. Use the incoming signaling-stack attribute as the authoritative inbound filter. Don't try to infer SIP-vs-ISUP origin from downstream behavior — check it directly, as early as possible (ideally before any other classification logic runs), the same way the source system does.
  2. Carry an explicit, persistent call-attribute bag through both stages. Attributes set early are read much later, by unrelated-looking steps. Point-to-point logic that doesn't model this will silently break whatever depends on it.
  3. Implement the exact match-precedence rules, not an approximation. Longest-explicit-match > longest-wildcard > attribute-specificity, with documented tie-breaking. We found a live example of what happens when two rules are ambiguous under these rules (arbitrary selection + a warning) — the new platform should either replicate that tolerance or, better, validate for overlapping rules at config time instead of resolving them silently at runtime.
  4. Preserve failover-set order exactly. It is a meaningful, intentional sequence, not an arbitrary list. Reordering changes real call outcomes.
  5. Preserve configured weights exactly wherever weighted-random selection is used.
  6. Keep Stage 2's single shared entry point as a structural invariant. Every successfully-classified call funnels through the same starting point before diverging by attribute — mirror that shape rather than duplicating destination-selection logic per upstream path.
  7. Map terminal outcomes 1:1, not just success/fail. At minimum: reject (with whatever announcement/error-code fidelity is needed), success. The rarer Stage-1 terminals should be explicitly confirmed absent from the SIP-eligible subset before being dropped — not assumed absent.
  8. Every current ISUP/TDM destination needs an actual SIP-facing proxy target to hand off to. This draft's whole outbound simplification rests on that gateway existing — it's an infrastructure/inventory dependency, not a call-flow-logic one, but it's load-bearing for the whole approach.

4. Explicit non-goals

5. What would most strengthen this draft next

  1. A real trace whose Destination Resolution currently ends at an ISUP/TDM circuit — now in scope under the outbound simplification, and we have zero examples of that resolution logic yet (our one full trace is a SIP-to-SIP path).
  2. Confirmation of exactly which values the signaling-stack attribute takes besides SIP and ISUP (e.g. MF/CAS?), and whether any of those also need to be treated as in-scope "SIP-equivalent" inbound sources.
  3. A real trace that hits a weighted-random selection set.
  4. A real trace that rejects in Stage 2 (we have plenty of Stage-1 reject examples, none for Stage 2 yet).
  5. Confirmation that the rarer Stage-1 terminals (SAC, restart, ANI-fail) never occur upstream of a SIP-bound call.