Change Document: v2.3.2.0 — API Page Breadcrumb Telemetry for Permission Failure Diagnosis
| Field | Value |
|---|---|
| Version | 2.3.2.0 |
| Date | 2026-03-09 |
| Extension | BC Dialing Application (Cambay Solutions) |
| Severity | Low — diagnostic telemetry only, no behavioral changes |
| Status | Implemented — ready for deployment |
Background
Production telemetry from March 9 reveals that 65% of ReceivePhoneNumber (Pag-80000) and 74% of ReceiveEmail (Pag-80003) API calls are returning HTTP 403 with NavPermissionException. The 403s come in pairs (Nextiva auto-retry), while successful 201s are singles. Both success and failure happen on the same pages within the same hour, indicating the failure is path-dependent — likely the new customer creation path hits tables not covered by the pages' inline Permissions property.
The existing telemetry (BCDIALER-4000, BCDIALER-4001, BCDIALER-0010) only fires after the customer lookup/creation succeeds. When a 403 aborts execution mid-trigger, no extension-level telemetry is emitted — the only evidence is the RT0008 event in environment-level telemetry, which shows the HTTP status but not which specific operation triggered the permission check.
This version adds breadcrumb-style telemetry at each stage of the OnInsertRecord trigger. When a permission failure aborts execution, the last breadcrumb logged identifies the exact operation that caused it.
Summary of Changes
| # | Severity | Category | Description |
|---|---|---|---|
| 1 | Low | Added | Step-through breadcrumb telemetry (BCDIALER-0020) on Pages 80000 and 80003 |
Detailed Changes
1. Breadcrumb Telemetry — BCDIALER-0020
Files modified:
src/Page/Pag-80000.ReceivePhoneNumber.al— 7 breadcrumb pointssrc/Page/Pag-80003.ReceiveEmail.al— 7 breadcrumb points
Breadcrumb steps:
| Step | ID | Fires Before | What It Tells You |
|---|---|---|---|
| 1 | 1-TriggerEntry | Config read | Request reached BC and the trigger fired. If this is missing, the 403 occurred before the trigger (page-level permission check) |
| 2 | 2-CustomerLookup | FindFirst() on Customer | Config loaded successfully. If execution stops here, the Customer table read failed |
| 3 | 3-NewCustStart | TemplateHeader.Get() | Customer lookup found no match — entering creation path. If this is the last step, the template Get failed |
| 4 | 4-PreInitCustomerNo | InitCustomerNo() | Template loaded. If execution stops here, InitCustomerNo hit a permission wall (No. Series tables) |
| 5 | 5-PreInsert | Insert(true) | Number assigned. If execution stops here, the Insert(true) trigger chain hit a missing permission |
| 6 | 6-PreApplyTemplate | ApplyCustomerTemplate() | Record inserted. If execution stops here, ApplyCustomerTemplate accessed an unpermitted table |
| 7 | 7-PreModify | Modify(true) | Template applied. If execution stops here, the final Modify(true) failed |
Custom dimensions per breadcrumb:
| Dimension | Present In | Description |
|---|---|---|
Source | All steps | Page identifier (Pag-80000 or Pag-80003) |
Step | All steps | Step identifier (e.g., 4-PreInitCustomerNo) |
PhoneNumber | Steps 1-2 (Pag-80000) | Inbound phone number |
Email | Steps 1-2 (Pag-80003) | Inbound email address |
TemplateName | Step 3 | Customer template being used |
CustomerNo | Steps 5-7 | Assigned customer number |
Diagnosis workflow:
- Query
BCDIALER-0020events and correlate withRT0008403 events by timestamp - For each 403, find the last
BCDIALER-0020breadcrumb within the same second - The step ID tells you exactly which operation triggered the permission failure
- Add the missing table to the page's
Permissionsproperty
Querying the Telemetry
Find the last breadcrumb before each 403:
let failures = traces
| where timestamp > ago(1d)
| where customDimensions.eventId == "RT0008"
| where customDimensions.alObjectName in ("ReceivePhoneNumber", "ReceiveEmail")
| where customDimensions.httpStatusCode == "403"
| project failureTime = timestamp, objectName = tostring(customDimensions.alObjectName);
traces
| where timestamp > ago(1d)
| where customDimensions.eventId == "ALBCDIALER-0020"
| project breadcrumbTime = timestamp,
Source = tostring(customDimensions.alSource),
Step = tostring(customDimensions.alStep),
Phone = tostring(customDimensions.alPhoneNumber),
Email = tostring(customDimensions.alEmail),
CustomerNo = tostring(customDimensions.alCustomerNo)
| order by breadcrumbTime desc
Summarize which step is the last one reached (failure point):
traces
| where timestamp > ago(1d)
| where customDimensions.eventId == "ALBCDIALER-0020"
| extend Source = tostring(customDimensions.alSource),
Step = tostring(customDimensions.alStep)
| summarize count() by Source, Step
| order by Source, Step
If Step 4 has significantly fewer events than Step 3, the failure is in InitCustomerNo. If Step 5 has fewer than Step 4, the failure is in Insert(true). And so on.
Object Inventory
No new objects. Modified objects only:
| Object | ID | Type | Status | Description |
|---|---|---|---|---|
| ReceivePhoneNumber | 80000 | Page | Modified | Added 7 BCDIALER-0020 breadcrumbs |
| ReceiveEmail | 80003 | Page | Modified | Added 7 BCDIALER-0020 breadcrumbs |
Deployment Notes
- No data migration required — telemetry-only change
- No configuration changes required
- Backwards compatible — additive telemetry, no behavioral changes
- Verification: After deployment, trigger an inbound call to a number not in BC (to exercise the new customer creation path) and confirm
BCDIALER-0020events appear in Application Insights with sequential step IDs
Known Limitations
- Breadcrumbs may not fire on page-level 403s. If the permission check fails before the
OnInsertRecordtrigger executes (e.g., the API user lacks read permission on the source table itself), noBCDIALER-0020events will be emitted. The absence of Step 1 combined with a 403 onRT0008indicates a page-level permission failure rather than a trigger-level one. - Telemetry ingestion delay — 2-10 minutes before events are queryable in Application Insights.