fix(llm): normalize caller IDs before the TRUSTED_CALLERS check - #29
Merged
Conversation
The FRITZ!Box delivers external callers in national format (015100000001) while operators naturally write E.164 in TRUSTED_CALLERS. _is_authorized did an exact string match, so an E.164 entry failed closed silently: the caller could converse but got no RAG/calendar access, with nothing indicating why. normalize_caller_id (agent/answer_policy.py) strips separators and maps 00 -> + and a leading 0 -> +49; LlmClient runs both the allowlist and the incoming caller ID through it. Only unambiguous dialling-plan transforms apply - internal extensions (**613), bare digits without a trunk prefix, and a withheld CLI stay exact-match, so the allowlist is never widened. The country code is hardcoded +49, matching the German trunk. Also replaces the operator's real mobile number in TODO.md with a placeholder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The FRITZ!Box delivers external callers in national format (
015100000001) while operators naturally write E.164 (+4915100000001) inTRUSTED_CALLERS.LlmClient._is_authorizeddid an exact string match, so an E.164 entry failed closed silently — the caller could converse but got no RAG/calendar access, with nothing in the logs indicating why. The allowlist is empty today, so this fixes the trap before it is first populated.Changes
agent/answer_policy.py— newnormalize_caller_id: strips separators (space,-,/,(,),.), maps00…→+…and a leading0→+49….agent/llm.py— both sides of the comparison run through it: the constructor normalizes allowlist entries,_is_authorizednormalizes the incoming caller ID and rejects an empty result.Why this does not widen the allowlist
This is an authorization boundary, so only unambiguous dialling-plan transforms apply. Anything that is not a plain number after separator removal is returned stripped but otherwise untouched, staying exact-match:
**613) — no E.164 form15100000001) — no country context, never guessedanonymous), and empty input — never authorized_COUNTRY_CODEis hardcoded+49, matching the German trunk; a non-German trunk needs it changed, and the code says so. Caller-ID authorization still trusts the SIP CLI, which is spoofable at the telephony layer — unchanged by this PR, and now called out in.env.example.Testing
TDD — every test watched failing before the implementation existed:
00/ four separator spellings / extension / bare digits /anonymous/ empty / whitespace), plus idempotence and distinctnesstest_normalization_does_not_widen_the_allowlist: different subscriber, bare digits,anonymous, empty,Noneall rejectedvenv/bin/pytest -q→ 337 passed.ruff format --check/ruff checkclean.from agent.main import mainimports.Docs
.env.exampledocuments that either format works and that extensions must match exactly;CLAUDE.mdtool-authorization decision updated; theTODO.mdlive-findings item is closed.Also replaces the operator's real mobile number with a placeholder in
TODO.md(it remains in earlier commit history).