Logging was configured correctly and shipping to Loki, but the stream was
useless: measured at 100% healthcheck chatter, with every line wrapped in ANSI
escape codes. The real SIP events were there and completely buried.
Three causes, each masking the next:
- asterisk.conf was written in the first lab commit with `nocolor = yes` and
never mounted, so the setting had no effect. Now mounted. It was also
overriding [directories] and runuser/rungroup, which the image sets up
correctly itself — removed, since overriding them risks breaking the
container for no gain.
- The image's command is `-vvvdddf`: verbosity 3 and debug 3 forced on the
command line, which overrides both asterisk.conf and logger.conf. Overridden
in compose to drop -v and -d; warnings and errors still log, and verbosity
is raisable at runtime when tracing a call.
- The actual source: the image's healthcheck makes ~7 separate `asterisk -rx`
connections every 30s, and Asterisk logs a connect/disconnect pair for each.
Replaced with a single check on a 60s interval, and the check now runs
`pjsip show transports` rather than `core show version` — that fails when
Asterisk is up but unconfigured, which is exactly the state that produced a
"healthy" container with no SIP stack on first deploy.
logger.conf drops both `notice` and `verbose`, which is where those pairs
arrive.
Verified on galatea: noise down from ~48 to 8 lines per two minutes (-83%),
zero ANSI codes in Loki, 90% of the stream now signal, container still
healthy, transport and dialplan intact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An Asterisk instance that answers calls, plays an IVR, holds with music and
connects a "human", so the gateway has something real to dial that is not the
PSTN: no charges, no strangers, no E911 exposure.
Asterisk rather than Kamailio because the unproven risks are media risks.
Kamailio is a proxy — it routes signalling and answers nothing, so it would
forward the INVITE and find nobody home. Asterisk is a B2BUA: it answers,
plays prompts and collects DTMF, which is the hold-slayer scenario itself.
Kamailio remains the better model for trunk registration/digest auth later.
No application changes are needed to use it. SIP_TRUNK_HOST is just an
address, so the production code path runs unmodified — there is no test-only
branch anywhere in the gateway. It also means safety is structural: while the
trunk points at the lab there is no route to the PSTN at all, an absence of
route rather than a policy that could be misconfigured.
Nine scenarios (1001-1008 plus an echo test) cover the baseline call, the
IVR/DTMF path, hold-then-human, long hold, busy, no-answer, remote hangup and
silence.
The image ships no sound files, so sounds/generate.py synthesises three
fixtures from fixed seeds — byte-identical on every run, which is what makes
a classifier regression distinguishable from noise. Verified against
AudioClassifier: music→MUSIC 0.85, speech→LIVE_HUMAN 0.75,
silence→SILENCE 1.00. The speech formants deliberately avoid the DTMF bands;
the first version landed on a valid pair and classified as a keypress.
Anonymous inbound calls are refused, and endpoint matching is by source
address — Asterisk's default matches the From-header domain, which Hold
Slayer populates from its SIP bind address (0.0.0.0 on a wildcard bind).
Generated audio and the rendered per-host configs are gitignored: the former
is reproducible from a fixed seed, the latter carry a host-specific IP and
the lab password.
Known limit, documented in the README: MediaPipeline.create_tap is a stub, so
the classifier receives no audio on a live call. RTP flows and Asterisk plays
audio, but the tap is never fed — the fixture results above were measured by
feeding the classifier directly. This blocks scenarios 1002/1003/1004.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>