sms-inbound: log exactly why recipient resolution failed

First live test dropped an inbound text with only "no owner found for
this DID, dropped" in the journal -- not enough to tell a config-dir
path problem from an ACL/permission problem from a DID-normalization
mismatch from an actually-unassigned DID, without re-triggering a real
text each time. resolve_recipients() now prints which of those it hit:
config dir unset, file missing (which also silently covers "smsrelay
can't traverse/read config/asterisk" -- os.path.isfile() swallows
PermissionError and just returns False), normalized DID has no section,
section has no owner=, or a group owner has no current members.

Nothing added is more sensitive than what's already visible on the
dashboard itself (config dir path, a DID's own digits, the set of DIDs
that exist) -- never the message body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDyKC6Kdg7tofmYSmRtgww
This commit is contained in:
Claude
2026-07-27 02:36:00 +00:00
parent 34638e8400
commit 3727f375b1
+23 -3
View File
@@ -270,23 +270,43 @@ def resolve_recipients(to_raw):
membership, so this always reflects who's in the group right now, not
who was in it when the DID was assigned. Empty list = nobody currently
owns this DID; the caller logs that and gives up rather than guessing
where to deliver it."""
where to deliver it.
Every early-return here also prints exactly why, since "no owner
found" alone gives no way to tell a path problem from a normalization
problem from an actually-unassigned DID without re-triggering a real
text. Nothing printed below is more sensitive than what's already
visible on the dashboard (config dir path, a DID's own digits, the
set of DIDs that exist) -- never the message body."""
if not ASTERISK_CONFIG_DIR:
print("resolve_recipients: SMS_ASTERISK_CONFIG_DIR is not set", flush=True)
return []
did = _normalize_did(to_raw)
if not did:
print("resolve_recipients: couldn't normalize to={!r} to a 10-digit DID".format(to_raw), flush=True)
return []
dids_cp = _read_conf(os.path.join(ASTERISK_CONFIG_DIR, "pstn-personal-dids.conf"))
dids_path = os.path.join(ASTERISK_CONFIG_DIR, "pstn-personal-dids.conf")
if not os.path.isfile(dids_path):
print("resolve_recipients: {} does not exist".format(dids_path), flush=True)
return []
dids_cp = _read_conf(dids_path)
if not dids_cp.has_section(did):
print("resolve_recipients: normalized did={} has no section in {} (known DIDs: {})".format(
did, dids_path, dids_cp.sections()), flush=True)
return []
owner = dids_cp.get(did, "owner", fallback="").strip()
if not owner:
print("resolve_recipients: did={} section has no owner= set".format(did), flush=True)
return []
if owner.startswith("@"):
groups_cp = _read_conf(os.path.join(ASTERISK_CONFIG_DIR, "pstn-groups.conf"))
group_name = owner[1:]
members_raw = groups_cp.get(group_name, "members", fallback="")
return [m.strip() for m in members_raw.split(",") if m.strip()]
members = [m.strip() for m in members_raw.split(",") if m.strip()]
if not members:
print("resolve_recipients: did={} owner=@{} but group has no members in pstn-groups.conf".format(
did, group_name), flush=True)
return members
return [owner]