Skip to content

feat(server): log who called, with optional labels for known numbers - #96

Merged
atdr merged 2 commits into
mainfrom
feat/identify-callers
Sep 27, 2026
Merged

atdr merged 2 commits into
mainfrom
feat/identify-callers

Conversation

@atdr

@atdr atdr commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • The journal recorded that a call arrived but nothing about who made it. twiml-response now carries from and forwardedFrom.
  • KNOWN_CALLERS optionally maps E.164 numbers to labels, so the log reads "caller":"Intercom" rather than a bare number, and anything unlabelled reads "caller":"unknown".
  • Descriptive only. Nothing is rejected on the strength of it.

Why

An unexpected buzz on 2026-09-17 could not be attributed from the journal. From arrives in the /twiml POST body and was parsed only for CallSid, so answering "who rang" meant opening the Twilio console. That is the second time in one evening we wanted this and did not have it.

forwardedFrom is logged as Twilio reports it. It was meant to tell a diverted call from a direct one, but hardware testing showed EE sets it to the dialled Twilio number on every call, so on this line it carries no information. The code comment now says so.

Independent of the hangup stack

Branched off main, not stacked on #88/#89/#95. It touches the TwiML handler and config rather than the hangup path, so it can merge in either order.

Changes

src/core/config.js

KNOWN_CALLERS parses comma-separated <E.164>=<label> pairs into a Map. Validation refuses an entry with no separator, a number that is not E.164, an empty or over-64-character label, and a number listed twice — the last because otherwise one label silently wins and the other is never seen again.

Split on the first = only, so a label may itself contain one (Gate = side door).

server.js

describeCaller() returns the label, 'unknown' for a number with none, and null when KNOWN_CALLERS is unset — so an unconfigured deployment omits the field rather than reading unknown on every call as though that were a finding.

Logged on twiml-response, after the Twilio signature check, never on twiml-request. twiml-request fires before the body is even read, and an unverified From is whatever the sender typed.

A repeated form field parses to an array (the signature covers the body, not its shape), so firstFormValue() takes the first value rather than letting .trim() throw.

Deliberately not a gate

You framed this as "primitive spam caller rejection", and I have built the identification half only. An unknown caller is labelled and still rings the doorbell.

Rejecting on this list would mean that the day the building management changes the panel's outbound number, or routes it through a different trunk, the doorbell silently stops working and nothing says why. A missed visitor at a door is a much worse failure than a labelled unknown call in a log. The data this adds is exactly what a later decision to filter would need, and by then there will be real traffic to judge it against — but that should be its own change, argued on its own terms.

The test file says this out loud, so a future change of policy has to delete the comment saying not to.

Test Plan

  • All five gates pass (typecheck, lint, format:check, check, test — 150 tests, up from 134).
  • tests/known-callers.test.cjs, 16 cases: parsing (pairs, padding, a label containing =), every validation failure, and labelling (known, unknown, unset, repeated From, whitespace).
  • Verified by mutation. Splitting on the last = fails 2, dropping the duplicate check fails 2, labelling when unconfigured fails 2, and dropping the trim on the incoming number fails 2. Both source files confirmed byte-identical afterwards.
  • Deployed with KNOWN_CALLERS set (2026-09-27). The intercom's number came from Twilio's call log (8 calls April to September), not a live call. Two live calls logged "from":"<Twilio number>","caller":"Self test", which confirms the path end to end.
  • Next real buzz reads "caller":"Intercom".

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rq7wnggNTrhwGMADfJ5yvf

@codecov

codecov Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.02439% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.95%. Comparing base (11c1820) to head (bc419ce).

Files with missing lines Patch % Lines
server.js 75.67% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #96      +/-   ##
==========================================
+ Coverage   69.47%   69.95%   +0.48%     
==========================================
  Files          14       14              
  Lines        3240     3322      +82     
==========================================
+ Hits         2251     2324      +73     
- Misses        989      998       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

An unexpected buzz on 2026-09-17 could not be attributed from the
journal at all. The call was recorded, the caller was not: From arrives
in the /twiml body and was parsed only for CallSid, so answering "who
rang" meant going to the Twilio console.

twiml-response now carries from and forwardedFrom. forwardedFrom is the
number that diverted the call, which is the signature of a building line
forwarding to us rather than someone dialling direct.

KNOWN_CALLERS optionally maps E.164 numbers to labels, so the journal
reads Intercom instead of a number, and anything else reads unknown.
That is the useful half: on a number only the building management is
supposed to have, telling the panel apart from a dialler that found an
open PSTN number is the whole question.

Logged after the signature check, never before. An unverified From is
whatever the sender typed, and twiml-request fires before the body is
even read.

Descriptive only, and deliberately not a gate. An unknown caller still
rings the doorbell: the building's dialler could change without warning,
and a silently blocked visitor is far worse than a labelled unknown one.
The label is also omitted entirely when nothing is configured, so an
unconfigured deployment does not read unknown on every call as though
that were a finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rq7wnggNTrhwGMADfJ5yvf
@atdr
atdr force-pushed the feat/identify-callers branch from 3a6460e to 9408842 Compare September 27, 2026 19:23
Hardware testing showed EE sets ForwardedFrom to the dialled Twilio
number on every inbound call, so it does not identify a diverting line
as the comment claimed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@atdr
atdr force-pushed the feat/identify-callers branch from 9408842 to bc419ce Compare September 27, 2026 19:24
@atdr
atdr merged commit 6b2eac8 into main Sep 27, 2026
9 checks passed
@atdr
atdr deleted the feat/identify-callers branch September 27, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant