Passa al contenuto principale

ADR-0025 - Rating-gated admission and LCR

  • Status: Accepted (2026-06-04)
  • Deciders: Massimo Bagnoli
  • Implementation tasks: TASK-464, TASK-465, TASK-466, TASK-467, TASK-468
  • Supersedes: nessuno
  • Superseded by: nessuno
  • Extends: ADR-0024 for offer-scoped customer admission cover

Context

During commissioning on 2026-06-04, Akira exposed a commercial correctness bug: a call can be routed and connected even when no applicable price list exists.

Today rating is applied only after call completion. The cdr-worker computes revenue and cost during CDR processing, and a missing tariff or rate raises a post-call rating error. At that point the call has already passed through the SIP path, so post-call accounting is too late to enforce commercial admission.

Rating must therefore become a pre-call admission gate as well as a post-call accounting step. A customer call without an applicable customer rate must not be routed. A terminator without an applicable provider rate must not be selected by LCR. If no rated terminator remains, the call must fail before relay.

This ADR is a successor capability, not an extension of ADR-0017 or ADR-0023: ADR-0017 keeps post-call billing in cdr-worker, while this decision adds pre-call SIP admission and rated LCR filtering.

Decision

  1. Rating is a pre-call gate, not only post-call accounting.

    A call without an applicable customer rate, or without any routed terminator that has an applicable provider rate, must be rejected with SIP 503 before it is connected. The CDR pipeline must preserve the rejection reason.

  2. Customer gate runs in or immediately after route[ADMISSION_CONTROL].

    After originator-to-company resolution and E.164 normalization, Kamailio must perform longest-prefix match of the called number against the active rates of the originator customer tariff.

    The lookup source is an in-memory htable populated by akira-kam-sync, with no database hit on the per-call SIP path:

    • htable: customer_rate_cover
    • key shape: {customer_tariff_id}:{prefix}
    • source data: active customer tariff rates

    ADR-0024 extends this table for company-scoped destination offers. When an active offer is scoped to specific companies, akira-kam-sync also exports {customer_tariff_id}:{company_id}:{prefix} for the concrete covered company/prefix pairs. Kamailio reads the scoped key first using the resolved originator company id and falls back to {customer_tariff_id}:{prefix}. This preserves the base gate while keeping admission coherent with CDR billing for scoped offer overrides.

    A miss rejects the call with:

    sl_send_reply("503", "No customer rate");

    The call must not enter routing after this rejection. The rejection reason is missed_customer_rate.

  3. Provider gate runs inside route[LCR_ROUTING].

    LCR must include only terminator candidates whose provider tariff has a rate covering the called prefix. Terminators without provider rate cover are filtered out before dispatcher selection or relay.

    The lookup source is a second in-memory htable populated by akira-kam-sync:

    • htable: provider_rate_cover
    • key shape: {provider_tariff_id}:{prefix}
    • source data: active provider tariff rates

    If no routed terminator remains after the provider-rate filter, Kamailio must reject the call with:

    sl_send_reply("503", "No rated route");

    The rejection reason is missed_provider_rate.

  4. CDRs for pre-dialog blocked calls are recorded through failed-transaction accounting.

    The existing dialog-based accounting path does not produce a CDR for calls rejected before dialog creation. For these admission 503 cases, Kamailio must enable failed-transaction accounting and propagate a rejection_reason field through:

    acc -> bridge -> cdr_consumer -> cdr.rejection_reason -> UI and reports.

    The canonical enum is:

    • missed_customer_rate
    • missed_provider_rate
    • no_route
    • not_authorized
    • suspended
    • null

    This makes CDR and reporting views explain why a blocked call failed instead of showing only a generic failed disposition.

  5. The per-call path uses in-memory LPM cover tables, not database lookups.

    akira-kam-sync builds the cover tables from tariff_rates, using only active tariffs and collapsed prefixes. For the 107k-prefix scale multiplied by active tariffs, only prefixes from active tariffs are pushed to Kamailio.

    Kamailio must not query PostgreSQL, call the backend rating engine, or depend on a network RPC to decide admission for each call.

  6. ADR-0025 is the canonical decision for this capability.

    The change adds a new architectural responsibility: rating now gates SIP admission and LCR in addition to post-call billing. Follow-up implementation tasks must align to this ADR before changing Kamailio config, htable sync, CDR schema, bridge behavior, backend worker logic, UI, or tests.

Consequences

Positive

  • Calls without commercial coverage stop before they connect.
  • Provider LCR no longer sends traffic to terminators that cannot be cost-rated.
  • CDRs and reports can distinguish missed_customer_rate, missed_provider_rate, no_route, not_authorized, and suspended.
  • The SIP hot path stays local to Kamailio memory structures.

Negative

  • Commissioning behavior changes immediately once implemented: routes that are not listed will stop passing. This is intended. Massimo must list the destinations before those calls are allowed to work.
  • akira-kam-sync must maintain two additional htables and keep their prefix coverage coherent with active tariff data.
  • Failed pre-dialog CDRs require an explicit failed-transaction accounting path in addition to the existing dialog-based accounting path.

Operational notes

  • Missing customer rate maps to SIP 503 with reason missed_customer_rate.
  • Missing provider route cover maps to SIP 503 with reason missed_provider_rate.
  • Generic absence of routing can continue to map to no_route; it must remain distinct from a rated-route miss.
  • Suspended and unauthorized admission failures should use suspended and not_authorized respectively when failed-transaction accounting is wired.

Alternatives considered

PostgreSQL lookup per call

Rejected. A database lookup on each SIP admission or LCR attempt would add latency and database coupling to the call setup path. It also makes rating availability depend on database responsiveness during every INVITE.

Backend rating-engine RPC from Kamailio

Rejected. An RPC to the backend rating engine would put a network service on the SIP critical path and introduce timeout/failure behavior that can block call setup. The rating engine remains authoritative for post-call accounting, but pre-call admission uses htable cover data pushed into Kamailio memory.

Keep rating as post-call only

Rejected. Post-call-only rating detects commercial gaps after the call has already connected, which is the commissioning bug this ADR fixes.

References

  • ADR-0017: Billing rating engine integration in cdr-worker.
  • ADR-0021: Kamailio to PostgreSQL SCRAM connection model for htable sync.
  • ADR-0022: Routing N-attempt editor on the current flat routing model.
  • ADR-0023: Tariffs UI parity on the existing tariff model.
  • ADR-0024: Offers as per-destination customer rate overrides.
  • TASK-464: CDR rejection_reason field end to end.
  • TASK-465: akira-kam-sync rate-cover htables.
  • TASK-466: Kamailio customer rate gate in admission control.
  • TASK-467: Kamailio provider rate filter in LCR.
  • TASK-468: Rating gate end-to-end test.