Commit Graph
57 Commits
Author SHA1 Message Date
iamdoubz 4496f6b89a outbound: routes with failover and caller ID, test box, Outbound rules page
Outbound routes CRUD, reorder and POST /test (mirrors the dialplan:
911 and internal numbers first, then the first matching enabled route,
class check, usable trunks, caller ID priority route > user > default).
The first trunk creates the default rules: toll-free first (because
1NXXNXXXXXX also matches 1800...), US/Canada, international disabled.

Generator: Asterisk orders patterns in one context by specificity, not
by file order, so each route gets its own sp-or-{id} context and
sp-outbound-routes includes them in position order (spec 03 §6.12
updated). sp-route-{id}: class check -> ss-noservice, caller ID
priority, per-trunk GROUP channel limit, failover on CHANUNAVAIL or
CONGESTION, all-circuits-busy-now when every trunk is full or off.

UI: Outbound rules page (↑/↓ reorder, pattern rows with help, ordered
trunk list, caller ID override, test box) and a Calling tab on users
(calling permission, outbound caller ID).

trunk-test.ts: 101 dials 3125550000 -> the fake provider receives
13125550000 with the DID in Remote-Party-ID (From user is the
sub-account), two-way audio; a caller without a class hears
ss-noservice. scripts/e2e/m3-t3.mjs covers the UI. With real voip.ms
still to do: whether per-call caller ID is honored (06 §4).

Task: M3-T3
2026-10-17 20:04:35 -05:00
iamdoubz c117d8bb1d dids: phone numbers, destinations, inbound dialplan, Phone numbers page
DIDs CRUD (any input format, normalized; 409 on duplicates; a number
that is an emergency or default caller ID can't be removed). Shared
destination service: assertDestinationExists, describeDestination
("Ring Dan Doe (101)") and findReferences over every destination
column in 02 §1 plus the unmatched-number setting; deleting an
extension a destination points at is now a 409 naming the places.

Generator: destApp (user, external, hangup for now), sp-from-trunk-*
per enabled trunk, sp-did-exists (left out when empty) and sp-inbound
with caller-name prefix. Golden case voipms-basic. Phone numbers page
with a minimal DestinationPicker (user, outside number, hang up).

trunk-test.ts: the fake provider calls the DID with a 10-digit
Request-URI like voip.ms; baresip 101 answers, caller ID arrives
normalized, 500 packets each way on both legs. scripts/e2e/m3-t2.mjs
checks the UI → Asterisk path. Still to do with real voip.ms: the
mobile-to-DID call and recording which header carries the DID (06 §3).

Task: M3-T2
2026-10-17 19:52:49 -05:00
iamdoubz 1b7f60b5f2 trunks: CRUD API, PJSIP trunk config, live registration status, Trunks page
voip.ms-first trunk form with the portal checklist, POP server list
and Test button; passwords are write-only and encrypted. Registration
and IP-auth trunks render per 03 §5.3 (TCP/TLS add ;transport=). The
API refuses a server name that doesn't resolve and transports that are
off, because either makes Asterisk drop the trunk.

Live state: registration from PJSIPShowRegistrationsOutbound (re-read
on every Registry event: two sub-accounts on one server share
Username/Domain), reachability from PJSIPShowEndpoint and trunk
ContactStatus (static contacts aren't in PJSIPShowContacts). Trunk
state is admin-only. Dashboard gets a "Trunks up" card.

trunk-test.ts (in pnpm test:asterisk) runs a fake voip.ms (second
Asterisk): the good trunk registers, a wrong password is Rejected,
qualify reaches the provider; its recorded AMI drives the unit tests.
scripts/e2e/m3-t1.mjs does the same through the UI. The acceptance
check with a real voip.ms sub-account is still to do.

Tests: settle() now awaits an apply instead of a fixed sleep (flaked
under full-suite load).

Task: M3-T1
2026-10-17 19:42:14 -05:00
iamdoubz c0c4036887 live: AMI-fed live state, /api/live WebSocket, dashboard and status dots
ci / check (push) Canceled after 0s
ci / images (push) Canceled after 0s
LiveState loads extension states and registered devices from Asterisk on
every AMI connect and follows ExtensionStatus/ContactStatus; the hub sends
a snapshot then deltas batched every 250 ms (devices for admins only),
pings every 30 s and closes unauthenticated sockets with 4401. The web UI
keeps one reconnecting socket in a zustand store, shows a Reconnecting
banner, a Dashboard (online, on a call, telephony engine) and status dots.

Fix: Asterisk 24 has no 'pjsip reload'; reload with
'module reload res_pjsip.so'. AMI errors now carry the CLI output.

Milestone check, automated: call-test.ts registers two baresip phones and
holds a 30 s call with RTP both ways (pnpm test:asterisk); the recorded
session drives the live-state tests. scripts/e2e/m2-live.mjs runs api +
Asterisk + phones + browser on a private Docker network: a new extension
is live in Asterisk in ~1.3 s, the dashboard shows 2/2 online and red dots
during the call.

Task: M2-T6
2026-10-17 16:49:25 -05:00
iamdoubz 1cfc13ead3 extensions: numbering service, CRUD API, credentials, Users page tabs
One namespace for internal numbers (range, reserved codes, parking, every
object table) with a next-free lookup. Creating an extension generates its
SIP password and voicemail PIN and a runtime-state row; every change and
password regeneration triggers a config apply. Credentials are revealed
only on request and each reveal is audited. Users get an Ext. column and a
drawer with General (number + next free), Phone and Credentials tabs.
scripts/e2e/m2-t5.mjs checks the flow in a browser and that the generated
PJSIP file follows; the files it produced load cleanly in Asterisk.

Task: M2-T5
2026-10-17 16:35:07 -05:00
iamdoubz 9a81e37725 users: admin CRUD API and admin shell UI
ci / check (push) Canceled after 0s
ci / images (push) Canceled after 0s
Login, forced password change, admin layout with every 05 §2 page in the
sidebar (unbuilt ones name their milestone), Users table/cards + drawer
(General tab), one-time temporary passwords, reset password. The last
active admin can't be demoted, deactivated or deleted. Audit diffs only
mask secret strings. scripts/e2e has a dependency-free CDP browser check
for this task's acceptance flow.

Task: M1-T4
2026-10-17 15:18:08 -05:00
iamdoubz d0de0c9039 deploy: docker compose stack, api image, install script, proxy configs
Postgres and AMI secrets are hex (the password goes into DATABASE_URL).

Task: M0-T6
2026-10-17 14:32:57 -05:00