FAILURE MAP
← Case archive

FA-96591 / Phone number formatting / Open access

tel URI builder: phone-context is written before the extension · case 01

Local numbers render ";phone-context=+42;ext=12".

Verified by executionVariant 1 · 9 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The context parameter is appended before the extension parameter.

THE FAILURE

The context parameter is appended before the extension parameter.

Unsuccessful approach: Keeping the order but writing the extension as a comma pause breaks every extension.

Case contract

Input {"number", optional "ext", optional "context"}. A number starting with "+" is global. Digits are grouped at runs of visual separators (- . space parentheses); any other character returns None. Build "tel:" + ("+" if global) + groups joined by "-", then ";ext=" + digits when an extension is given, then for local numbers ";phone-context=" + context (required).

Why this case matters

tel URIs are exchanged between softphones, CRMs and SIP gateways; a missing context or misordered parameter makes local numbers undialable.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    num, ext, ctx = x['number'].strip(), x.get('ext', ''), x.get('context', '')
    glob = num.startswith('+')
    body = num[1:] if glob else num
    parts, cur = [], ''
    for c in body:
        if c.isdigit():
            cur += c
        elif c in '-. ()':
            if cur:
                parts.append(cur)
                cur = ''
        else:
            return None
    if cur:
        parts.append(cur)
    if not parts:
        return None
    uri = 'tel:' + ('+' if glob else '') + '-'.join(parts)
    if not glob:
        if not ctx:
            return None
        uri += ';phone-context=' + ctx
    if ext:
        if not ext.isdigit():
            return None
        uri += ';ext=' + ext
    return uri
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['global number', {'number': '+42 555-010-1211'}, 'tel:+42-555-010-1211'], ['local number with context', {'number': '555-0114', 'context': '+42'}, 'tel:555-0114;phone-context=+42'], ['local number with extension', {'number': '555-0114', 'ext': '12', 'context': '+42'}, 'tel:555-0114;ext=12;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0114', 'ext': '1'}, 'tel:+42-555-0114;ext=1'], ['local without context', {'number': '5550114'}, None], ['run of separators', {'number': '+42 555 - 0114'}, 'tel:+42-555-0114'], ['slash is not a visual separator', {'number': '+42 555/0114'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0114', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1222'}, 'tel:+42-555-010-1222'], ['local number with context', {'number': '555-0125', 'context': '+42'}, 'tel:555-0125;phone-context=+42'], ['local number with extension', {'number': '555-0125', 'ext': '22', 'context': '+42'}, 'tel:555-0125;ext=22;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0125', 'ext': '2'}, 'tel:+42-555-0125;ext=2'], ['local without context', {'number': '5550125'}, None], ['run of separators', {'number': '+42 555 - 0125'}, 'tel:+42-555-0125'], ['slash is not a visual separator', {'number': '+42 555/0125'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0125', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1233'}, 'tel:+42-555-010-1233'], ['local number with context', {'number': '555-0136', 'context': '+42'}, 'tel:555-0136;phone-context=+42'], ['local number with extension', {'number': '555-0136', 'ext': '32', 'context': '+42'}, 'tel:555-0136;ext=32;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0136', 'ext': '3'}, 'tel:+42-555-0136;ext=3'], ['local without context', {'number': '5550136'}, None], ['run of separators', {'number': '+42 555 - 0136'}, 'tel:+42-555-0136'], ['slash is not a visual separator', {'number': '+42 555/0136'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0136', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1244'}, 'tel:+42-555-010-1244'], ['local number with context', {'number': '555-0147', 'context': '+42'}, 'tel:555-0147;phone-context=+42'], ['local number with extension', {'number': '555-0147', 'ext': '42', 'context': '+42'}, 'tel:555-0147;ext=42;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0147', 'ext': '4'}, 'tel:+42-555-0147;ext=4'], ['local without context', {'number': '5550147'}, None], ['run of separators', {'number': '+42 555 - 0147'}, 'tel:+42-555-0147'], ['slash is not a visual separator', {'number': '+42 555/0147'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0147', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1255'}, 'tel:+42-555-010-1255'], ['local number with context', {'number': '555-0158', 'context': '+42'}, 'tel:555-0158;phone-context=+42'], ['local number with extension', {'number': '555-0158', 'ext': '52', 'context': '+42'}, 'tel:555-0158;ext=52;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0158', 'ext': '5'}, 'tel:+42-555-0158;ext=5'], ['local without context', {'number': '5550158'}, None], ['run of separators', {'number': '+42 555 - 0158'}, 'tel:+42-555-0158'], ['slash is not a visual separator', {'number': '+42 555/0158'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0158', 'ext': '1a'}, None]]]
for label, arg, expected in fixtures[N - 1]:
    check(label, solve(arg), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
global numbertel:+42-555-010-1211tel:+42-555-010-1211Passed
local number with contexttel:555-0114;phone-context=+42tel:555-0114;phone-context=+42Passed
local number with extensiontel:555-0114;phone-context=+42;ext=12tel:555-0114;ext=12;phone-context=+42Failed
global with extensiontel:+42-555-0114;ext=1tel:+42-555-0114;ext=1Passed
local without contextNoneNonePassed
run of separatorstel:+42-555-0114tel:+42-555-0114Passed
slash is not a visual separatorNoneNonePassed
letters rejectedNoneNonePassed
non-numeric extensionNoneNonePassed

SHA-256 / bb62a9f44482810300f0fed05617c52e4f4266f2aefeb5da2b4774c12154bf9c

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    num, ext, ctx = x['number'].strip(), x.get('ext', ''), x.get('context', '')
    glob = num.startswith('+')
    body = num[1:] if glob else num
    parts, cur = [], ''
    for c in body:
        if c.isdigit():
            cur += c
        elif c in '-. ()':
            if cur:
                parts.append(cur)
                cur = ''
        else:
            return None
    if cur:
        parts.append(cur)
    if not parts:
        return None
    uri = 'tel:' + ('+' if glob else '') + '-'.join(parts)
    if ext:
        if not ext.isdigit():
            return None
        uri += ',' + ext
    if not glob:
        if not ctx:
            return None
        uri += ';phone-context=' + ctx
    return uri
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['global number', {'number': '+42 555-010-1211'}, 'tel:+42-555-010-1211'], ['local number with context', {'number': '555-0114', 'context': '+42'}, 'tel:555-0114;phone-context=+42'], ['local number with extension', {'number': '555-0114', 'ext': '12', 'context': '+42'}, 'tel:555-0114;ext=12;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0114', 'ext': '1'}, 'tel:+42-555-0114;ext=1'], ['local without context', {'number': '5550114'}, None], ['run of separators', {'number': '+42 555 - 0114'}, 'tel:+42-555-0114'], ['slash is not a visual separator', {'number': '+42 555/0114'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0114', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1222'}, 'tel:+42-555-010-1222'], ['local number with context', {'number': '555-0125', 'context': '+42'}, 'tel:555-0125;phone-context=+42'], ['local number with extension', {'number': '555-0125', 'ext': '22', 'context': '+42'}, 'tel:555-0125;ext=22;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0125', 'ext': '2'}, 'tel:+42-555-0125;ext=2'], ['local without context', {'number': '5550125'}, None], ['run of separators', {'number': '+42 555 - 0125'}, 'tel:+42-555-0125'], ['slash is not a visual separator', {'number': '+42 555/0125'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0125', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1233'}, 'tel:+42-555-010-1233'], ['local number with context', {'number': '555-0136', 'context': '+42'}, 'tel:555-0136;phone-context=+42'], ['local number with extension', {'number': '555-0136', 'ext': '32', 'context': '+42'}, 'tel:555-0136;ext=32;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0136', 'ext': '3'}, 'tel:+42-555-0136;ext=3'], ['local without context', {'number': '5550136'}, None], ['run of separators', {'number': '+42 555 - 0136'}, 'tel:+42-555-0136'], ['slash is not a visual separator', {'number': '+42 555/0136'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0136', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1244'}, 'tel:+42-555-010-1244'], ['local number with context', {'number': '555-0147', 'context': '+42'}, 'tel:555-0147;phone-context=+42'], ['local number with extension', {'number': '555-0147', 'ext': '42', 'context': '+42'}, 'tel:555-0147;ext=42;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0147', 'ext': '4'}, 'tel:+42-555-0147;ext=4'], ['local without context', {'number': '5550147'}, None], ['run of separators', {'number': '+42 555 - 0147'}, 'tel:+42-555-0147'], ['slash is not a visual separator', {'number': '+42 555/0147'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0147', 'ext': '1a'}, None]], [['global number', {'number': '+42 555-010-1255'}, 'tel:+42-555-010-1255'], ['local number with context', {'number': '555-0158', 'context': '+42'}, 'tel:555-0158;phone-context=+42'], ['local number with extension', {'number': '555-0158', 'ext': '52', 'context': '+42'}, 'tel:555-0158;ext=52;phone-context=+42'], ['global with extension', {'number': '+42 (555) 0158', 'ext': '5'}, 'tel:+42-555-0158;ext=5'], ['local without context', {'number': '5550158'}, None], ['run of separators', {'number': '+42 555 - 0158'}, 'tel:+42-555-0158'], ['slash is not a visual separator', {'number': '+42 555/0158'}, None], ['letters rejected', {'number': '+42 555 FAKE'}, None], ['non-numeric extension', {'number': '+42 555 0158', 'ext': '1a'}, None]]]
for label, arg, expected in fixtures[N - 1]:
    check(label, solve(arg), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
global numbertel:+42-555-010-1211tel:+42-555-010-1211Passed
local number with contexttel:555-0114;phone-context=+42tel:555-0114;phone-context=+42Passed
local number with extensiontel:555-0114,12;phone-context=+42tel:555-0114;ext=12;phone-context=+42Failed
global with extensiontel:+42-555-0114,1tel:+42-555-0114;ext=1Failed
local without contextNoneNonePassed
run of separatorstel:+42-555-0114tel:+42-555-0114Passed
slash is not a visual separatorNoneNonePassed
letters rejectedNoneNonePassed
non-numeric extensionNoneNonePassed

SHA-256 / 57711fbe14f98027b2a1ce9150f967faf2459ccd237b82930b8a4d9d1053788c

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 9 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

A deterministic toy contract over synthetic, obviously fictitious telephone numbers and toy country tables. The rules are stipulated for this model only and make no claim of conformance with any numbering plan or telecommunications standard. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:52:24.338374+00:00.

Case digest / 966495573efe88d55993a35e3251d35bc92dc71738c460302daea8ba25a80e32